Python code modernization, 1/n (#1141)

* Remove unicode text prefixes.

* Replace str.format() uses with f-strings.

* Replace % with f-strings, and do some cleanup.

* Fix wrong variable.

* Avoid unnecessary string conversion.
This commit is contained in:
Felix Fontein
2025-10-06 18:30:54 +02:00
committed by GitHub
parent 1f2817fa20
commit f45232635c
93 changed files with 930 additions and 1122 deletions
+16 -22
View File
@@ -55,15 +55,15 @@ class APIClient(
>>> import docker
>>> client = docker.APIClient(base_url='unix://var/run/docker.sock')
>>> client.version()
{u'ApiVersion': u'1.33',
u'Arch': u'amd64',
u'BuildTime': u'2017-11-19T18:46:37.000000000+00:00',
u'GitCommit': u'f4ffd2511c',
u'GoVersion': u'go1.9.2',
u'KernelVersion': u'4.14.3-1-ARCH',
u'MinAPIVersion': u'1.12',
u'Os': u'linux',
u'Version': u'17.10.0-ce'}
{'ApiVersion': '1.33',
'Arch': 'amd64',
'BuildTime': '2017-11-19T18:46:37.000000000+00:00',
'GitCommit': 'f4ffd2511c',
'GoVersion': 'go1.9.2',
'KernelVersion': '4.14.3-1-ARCH',
'MinAPIVersion': '1.12',
'Os': 'linux',
'Version': '17.10.0-ce'}
Args:
base_url (str): URL to the Docker server. For example,
@@ -187,14 +187,11 @@ class APIClient(
self._version = version
if not isinstance(self._version, str):
raise DockerException(
'Version parameter must be a string or None. Found {0}'.format(
type(version).__name__
)
f'Version parameter must be a string or None. Found {type(version).__name__}'
)
if utils.version_lt(self._version, MINIMUM_DOCKER_API_VERSION):
raise InvalidVersion(
'API versions below {0} are no longer supported by this '
'library.'.format(MINIMUM_DOCKER_API_VERSION)
f'API versions below {MINIMUM_DOCKER_API_VERSION} are no longer supported by this library.'
)
def _retrieve_server_version(self):
@@ -202,7 +199,7 @@ class APIClient(
version_result = self.version(api_version=False)
except Exception as e:
raise DockerException(
'Error while fetching server API version: {0}'.format(e)
f'Error while fetching server API version: {e}'
)
try:
@@ -214,7 +211,7 @@ class APIClient(
)
except Exception as e:
raise DockerException(
'Error while fetching server API version: {0}. Response seems to be broken.'.format(e)
f'Error while fetching server API version: {e}. Response seems to be broken.'
)
def _set_request_timeout(self, kwargs):
@@ -247,19 +244,16 @@ class APIClient(
for arg in args:
if not isinstance(arg, str):
raise ValueError(
'Expected a string but found {0} ({1}) '
'instead'.format(arg, type(arg))
f'Expected a string but found {arg} ({type(arg)}) instead'
)
quote_f = partial(quote, safe="/:")
args = map(quote_f, args)
if kwargs.get('versioned_api', True):
return '{0}/v{1}{2}'.format(
self.base_url, self._version, pathfmt.format(*args)
)
return f'{self.base_url}/v{self._version}{pathfmt.format(*args)}'
else:
return '{0}{1}'.format(self.base_url, pathfmt.format(*args))
return f'{self.base_url}{pathfmt.format(*args)}'
def _raise_for_status(self, response):
"""Raises stored :class:`APIError`, if one occurred."""
+5 -8
View File
@@ -19,7 +19,7 @@ from .credentials.errors import StoreError, CredentialsNotFound
from .utils import config
INDEX_NAME = 'docker.io'
INDEX_URL = 'https://index.{0}/v1/'.format(INDEX_NAME)
INDEX_URL = f'https://index.{INDEX_NAME}/v1/'
TOKEN_USERNAME = '<token>'
log = logging.getLogger(__name__)
@@ -28,14 +28,13 @@ log = logging.getLogger(__name__)
def resolve_repository_name(repo_name):
if '://' in repo_name:
raise errors.InvalidRepository(
'Repository name cannot contain a scheme ({0})'.format(repo_name)
f'Repository name cannot contain a scheme ({repo_name})'
)
index_name, remote_name = split_repo_name(repo_name)
if index_name[0] == '-' or index_name[-1] == '-':
raise errors.InvalidRepository(
'Invalid index name ({0}). Cannot begin or end with a'
' hyphen.'.format(index_name)
f'Invalid index name ({index_name}). Cannot begin or end with a hyphen.'
)
return resolve_index_name(index_name), remote_name
@@ -117,9 +116,7 @@ class AuthConfig(dict):
# keys is not formatted properly.
if raise_on_error:
raise errors.InvalidConfigFile(
'Invalid configuration for registry {0}'.format(
registry
)
f'Invalid configuration for registry {registry}'
)
return {}
if 'identitytoken' in entry:
@@ -272,7 +269,7 @@ class AuthConfig(dict):
return None
except StoreError as e:
raise errors.DockerException(
'Credentials store error: {0}'.format(repr(e))
f'Credentials store error: {e}'
)
def _get_store_instance(self, name):
+3 -3
View File
@@ -146,14 +146,14 @@ class ContextAPI(object):
names.append(name)
except Exception as e:
raise errors.ContextException(
"Failed to load metafile {filepath}: {e}".format(filepath=filepath, e=e),
f"Failed to load metafile {filepath}: {e}"
) from e
contexts = [cls.get_default_context()]
for name in names:
context = Context.load_context(name)
if not context:
raise errors.ContextException("Context {context} cannot be found".format(context=name))
raise errors.ContextException(f"Context {name} cannot be found")
contexts.append(context)
return contexts
@@ -174,7 +174,7 @@ class ContextAPI(object):
err = write_context_name_to_docker_config(name)
if err:
raise errors.ContextException(
'Failed to set current context: {err}'.format(err=err))
f'Failed to set current context: {err}')
@classmethod
def remove_context(cls, name):
+1 -1
View File
@@ -29,7 +29,7 @@ def get_current_context_name_with_source():
if docker_cfg_path:
try:
with open(docker_cfg_path) as f:
return json.load(f).get("currentContext", "default"), "configuration file {file}".format(file=docker_cfg_path)
return json.load(f).get("currentContext", "default"), f"configuration file {docker_cfg_path}"
except Exception:
pass
return "default", "fallback value"
+3 -3
View File
@@ -62,7 +62,7 @@ class Context(object):
if not isinstance(v, dict):
# unknown format
raise ContextException(
"Unknown endpoint format for context {name}: {v}".format(name=name, v=v),
f"Unknown endpoint format for context {name}: {v}",
)
self.endpoints[k] = v
@@ -118,7 +118,7 @@ class Context(object):
except (OSError, KeyError, ValueError) as e:
# unknown format
raise Exception(
"Detected corrupted meta file for context {name} : {e}".format(name=name, e=e)
f"Detected corrupted meta file for context {name} : {e}"
) from e
# for docker endpoints, set defaults for
@@ -193,7 +193,7 @@ class Context(object):
rmtree(self.tls_path)
def __repr__(self):
return "<{classname}: '{name}'>".format(classname=self.__class__.__name__, name=self.name)
return f"<{self.__class__.__name__}: '{self.name}'>"
def __str__(self):
return json.dumps(self.__call__(), indent=2)
@@ -26,12 +26,8 @@ def process_store_error(cpe, program):
message = cpe.output.decode('utf-8')
if 'credentials not found in native keychain' in message:
return CredentialsNotFound(
'No matching credentials in {0}'.format(
program
)
f'No matching credentials in {program}'
)
return StoreError(
'Credentials store {0} exited with "{1}".'.format(
program, cpe.output.decode('utf-8').strip()
)
f'Credentials store {program} exited with "{cpe.output.decode("utf-8").strip()}".'
)
+4 -10
View File
@@ -30,9 +30,7 @@ class Store(object):
self.environment = environment
if self.exe is None:
raise errors.InitializationError(
'{0} not installed or not available in PATH'.format(
self.program
)
f'{self.program} not installed or not available in PATH'
)
def get(self, server):
@@ -50,7 +48,7 @@ class Store(object):
# raise CredentialsNotFound
if result['Username'] == '' and result['Secret'] == '':
raise errors.CredentialsNotFound(
'No matching credentials in {0}'.format(self.program)
f'No matching credentials in {self.program}'
)
return result
@@ -92,14 +90,10 @@ class Store(object):
except OSError as e:
if e.errno == errno.ENOENT:
raise errors.StoreError(
'{0} not installed or not available in PATH'.format(
self.program
)
f'{self.program} not installed or not available in PATH'
)
else:
raise errors.StoreError(
'Unexpected OS error "{0}", errno={1}'.format(
e.strerror, e.errno
)
f'Unexpected OS error "{e.strerror}", errno={e.errno}'
)
return output
+10 -15
View File
@@ -59,17 +59,13 @@ class APIError(_HTTPError, DockerException):
message = super(APIError, self).__str__()
if self.is_client_error():
message = '{0} Client Error for {1}: {2}'.format(
self.response.status_code, self.response.url,
self.response.reason)
message = f'{self.response.status_code} Client Error for {self.response.url}: {self.response.reason}'
elif self.is_server_error():
message = '{0} Server Error for {1}: {2}'.format(
self.response.status_code, self.response.url,
self.response.reason)
message = f'{self.response.status_code} Server Error for {self.response.url}: {self.response.reason}'
if self.explanation:
message = '{0} ("{1}")'.format(message, self.explanation)
message = f'{message} ("{self.explanation}")'
return message
@@ -146,9 +142,8 @@ class ContainerError(DockerException):
self.image = image
self.stderr = stderr
err = ": {0}".format(stderr) if stderr is not None else ""
msg = ("Command '{0}' in image '{1}' returned non-zero exit "
"status {2}{3}").format(command, image, exit_status, err)
err = f": {stderr}" if stderr is not None else ""
msg = f"Command '{command}' in image '{image}' returned non-zero exit status {exit_status}{err}"
super(ContainerError, self).__init__(msg)
@@ -170,8 +165,8 @@ class ImageLoadError(DockerException):
def create_unexpected_kwargs_error(name, kwargs):
quoted_kwargs = ["'{0}'".format(k) for k in sorted(kwargs)]
text = ["{0}() ".format(name)]
quoted_kwargs = [f"'{k}'" for k in sorted(kwargs)]
text = [f"{name}() "]
if len(quoted_kwargs) == 1:
text.append("got an unexpected keyword argument ")
else:
@@ -185,7 +180,7 @@ class MissingContextParameter(DockerException):
self.param = param
def __str__(self):
return ("missing parameter: {0}".format(self.param))
return f"missing parameter: {self.param}"
class ContextAlreadyExists(DockerException):
@@ -193,7 +188,7 @@ class ContextAlreadyExists(DockerException):
self.name = name
def __str__(self):
return ("context {0} already exists".format(self.name))
return f"context {self.name} already exists"
class ContextException(DockerException):
@@ -209,7 +204,7 @@ class ContextNotFound(DockerException):
self.name = name
def __str__(self):
return ("context '{0}' not found".format(self.name))
return f"context '{self.name}' not found"
class MissingRequirementException(DockerException):
+3 -8
View File
@@ -107,7 +107,7 @@ def create_archive(root, files=None, fileobj=None, gzip=False,
t.addfile(i, f)
except IOError:
raise IOError(
'Can not read file in context: {0}'.format(full_path)
f'Can not read file in context: {full_path}'
)
else:
# Directories, FIFOs, symlinks... do not need to be read.
@@ -271,18 +271,13 @@ def process_dockerfile(dockerfile, path):
abs_dockerfile = os.path.join(path, dockerfile)
if IS_WINDOWS_PLATFORM and path.startswith(
WINDOWS_LONGPATH_PREFIX):
abs_dockerfile = '{0}{1}'.format(
WINDOWS_LONGPATH_PREFIX,
os.path.normpath(
abs_dockerfile[len(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 (
'.dockerfile.{random:x}'.format(random=random.getrandbits(160)),
f'.dockerfile.{random.getrandbits(160):x}',
df.read()
)
@@ -38,9 +38,7 @@ def minimum_version(version):
def wrapper(self, *args, **kwargs):
if utils.version_lt(self._version, version):
raise errors.InvalidVersion(
'{0} is not available for version < {1}'.format(
f.__name__, version
)
f'{f.__name__} is not available for version < {version}'
)
return f(self, *args, **kwargs)
return wrapper
+1 -1
View File
@@ -119,7 +119,7 @@ def translate(pat):
stuff = '^' + stuff[1:]
elif stuff[0] == '^':
stuff = '\\' + stuff
res = '%s[%s]' % (res, stuff)
res = f'{res}[{stuff}]'
else:
res = res + re.escape(c)
@@ -52,7 +52,7 @@ def json_stream(stream):
return split_buffer(stream, json_splitter, json_decoder.decode)
def line_splitter(buffer, separator=u'\n'):
def line_splitter(buffer, separator='\n'):
index = buffer.find(str(separator))
if index == -1:
return None
+5 -5
View File
@@ -49,19 +49,19 @@ def build_port_bindings(ports):
def _raise_invalid_port(port):
raise ValueError('Invalid port "%s", should be '
raise ValueError(f'Invalid port "{port}", should be '
'[[remote_ip:]remote_port[-remote_port]:]'
'port[/protocol]' % port)
'port[/protocol]')
def port_range(start, end, proto, randomly_available_port=False):
if not start:
return start
if not end:
return [start + proto]
return [f'{start}{proto}']
if randomly_available_port:
return ['{0}-{1}'.format(start, end) + proto]
return [str(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):
+1 -2
View File
@@ -80,5 +80,4 @@ class ProxyConfig(dict):
return proxy_env + environment
def __str__(self):
return 'ProxyConfig(http={0}, https={1}, ftp={2}, no_proxy={3})'.format(
self.http, self.https, self.ftp, self.no_proxy)
return f'ProxyConfig(http={self.http}, https={self.https}, ftp={self.ftp}, no_proxy={self.no_proxy})'
+2 -2
View File
@@ -169,7 +169,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('frame must be (None, None), but got %s' % (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 +193,4 @@ def demux_adaptor(stream_id, data):
elif stream_id == STDERR:
return (None, data)
else:
raise ValueError('{0} is not a valid stream'.format(stream_id))
raise ValueError(f'{stream_id} is not a valid stream')
+21 -30
View File
@@ -136,8 +136,7 @@ def convert_volume_binds(binds):
if isinstance(v, dict):
if 'ro' in v and 'mode' in v:
raise ValueError(
'Binding cannot contain both "ro" and "mode": {0}'
.format(repr(v))
f'Binding cannot contain both "ro" and "mode": {v!r}'
)
bind = v['bind']
@@ -167,11 +166,11 @@ def convert_volume_binds(binds):
else:
mode = v['propagation']
result.append('{0}:{1}:{2}'.format(k, bind, mode))
result.append(f'{k}:{bind}:{mode}')
else:
if isinstance(v, bytes):
v = v.decode('utf-8')
result.append('{0}:{1}:rw'.format(k, v))
result.append(f'{k}:{v}:rw')
return result
@@ -181,8 +180,7 @@ def convert_tmpfs_mounts(tmpfs):
if not isinstance(tmpfs, list):
raise ValueError(
'Expected tmpfs value to be either a list or a dict, found: {0}'
.format(type(tmpfs).__name__)
f'Expected tmpfs value to be either a list or a dict, found: {type(tmpfs).__name__}'
)
result = {}
@@ -196,8 +194,7 @@ def convert_tmpfs_mounts(tmpfs):
else:
raise ValueError(
"Expected item in tmpfs list to be a string, found: {0}"
.format(type(mount).__name__)
f"Expected item in tmpfs list to be a string, found: {type(mount).__name__}"
)
result[name] = options
@@ -257,14 +254,14 @@ def parse_host(addr, is_win32=False, tls=False):
if proto not in ('tcp', 'unix', 'npipe', 'ssh'):
raise errors.DockerException(
"Invalid bind address protocol: {0}".format(addr)
f"Invalid bind address protocol: {addr}"
)
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(
'Invalid bind address format: {0}'.format(addr)
f'Invalid bind address format: {addr}'
)
if any([
@@ -272,13 +269,12 @@ def parse_host(addr, is_win32=False, tls=False):
parsed_url.password
]):
raise errors.DockerException(
'Invalid bind address format: {0}'.format(addr)
f'Invalid bind address format: {addr}'
)
if parsed_url.path and proto == 'ssh':
raise errors.DockerException(
'Invalid bind address format: no path allowed for this protocol:'
' {0}'.format(addr)
f'Invalid bind address format: no path allowed for this protocol: {addr}'
)
else:
path = parsed_url.path
@@ -292,19 +288,19 @@ def parse_host(addr, is_win32=False, tls=False):
port = parsed_url.port or 0
if port <= 0:
port = 22 if proto == 'ssh' else (2375 if tls else 2376)
netloc = '{0}:{1}'.format(parsed_url.netloc, port)
netloc = f'{parsed_url.netloc}:{port}'
if not parsed_url.hostname:
netloc = '{0}:{1}'.format(DEFAULT_HTTP_HOST, port)
netloc = f'{DEFAULT_HTTP_HOST}:{port}'
# Rewrite schemes to fit library internals (requests adapters)
if proto == 'tcp':
proto = 'http{0}'.format('s' if tls else '')
proto = f"http{'s' if tls else ''}"
elif proto == 'unix':
proto = 'http+unix'
if proto in ('http+unix', 'npipe'):
return "{0}://{1}".format(proto, path).rstrip('/')
return f"{proto}://{path}".rstrip('/')
return urlunparse(URLComponents(
scheme=proto,
netloc=netloc,
@@ -323,7 +319,7 @@ def parse_devices(devices):
continue
if not isinstance(device, str):
raise errors.DockerException(
'Invalid device type {0}'.format(type(device))
f'Invalid device type {type(device)}'
)
device_mapping = device.split(':')
if device_mapping:
@@ -428,17 +424,14 @@ def parse_bytes(s):
digits = float(digits_part)
except ValueError:
raise errors.DockerException(
'Failed converting the string value for memory ({0}) to'
' an integer.'.format(digits_part)
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(
'The specified value for memory ({0}) should specify the'
' units. The postfix should be one of the `b` `k` `m` `g`'
' characters'.format(s)
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
@@ -448,7 +441,7 @@ def normalize_links(links):
if isinstance(links, dict):
links = links.items()
return ['{0}:{1}'.format(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):
@@ -473,9 +466,7 @@ def parse_env_file(env_file):
k, v = parse_line
environment[k] = v
else:
raise errors.DockerException(
'Invalid line in environment file {0}:\n{1}'.format(
env_file, line))
raise errors.DockerException(f'Invalid line in environment file {env_file}:\n{line}')
return environment
@@ -491,7 +482,7 @@ def format_environment(environment):
if isinstance(value, bytes):
value = value.decode('utf-8')
return u'{key}={value}'.format(key=key, value=value)
return f'{key}={value}'
return [format_env(*var) for var in environment.items()]
@@ -499,11 +490,11 @@ def format_extra_hosts(extra_hosts, task=False):
# Use format dictated by Swarm API if container is part of a task
if task:
return [
'{0} {1}'.format(v, k) for k, v in sorted(extra_hosts.items())
f'{v} {k}' for k, v in sorted(extra_hosts.items())
]
return [
'{0}:{1}'.format(k, v) for k, v in sorted(extra_hosts.items())
f'{k}:{v}' for k, v in sorted(extra_hosts.items())
]
+3 -2
View File
@@ -98,7 +98,7 @@ class _Parser(object):
try:
v += _HEX_DICT[self.line[self.index]]
except KeyError:
raise InvalidLogFmt('Invalid unicode escape digit {digit!r}'.format(digit=self.line[self.index]))
raise InvalidLogFmt(f'Invalid unicode escape digit {self.line[self.index]!r}')
self.index += 6
return chr(v)
@@ -170,7 +170,8 @@ def parse_line(line, logrus_mode=False):
if cur in _ESCAPE_DICT:
value.append(_ESCAPE_DICT[cur])
elif cur != 'u':
raise InvalidLogFmt('Unknown escape sequence {seq!r}'.format(seq='\\' + cur))
es = f"\\{cur}"
raise InvalidLogFmt(f'Unknown escape sequence {es!r}')
else:
parser.prev()
value.append(parser.parse_unicode_sequence())
+7 -7
View File
@@ -18,9 +18,9 @@ _VALID_STR = re.compile('^[A-Za-z0-9_-]+$')
def _validate_part(string, part, part_name):
if not part:
raise ValueError('Invalid platform string "{string}": {part} is empty'.format(string=string, part=part_name))
raise ValueError(f'Invalid platform string "{string}": {part_name} is empty')
if not _VALID_STR.match(part):
raise ValueError('Invalid platform string "{string}": {part} has invalid characters'.format(string=string, part=part_name))
raise ValueError(f'Invalid platform string "{string}": {part_name} has invalid characters')
return part
@@ -123,16 +123,16 @@ class _Platform(object):
arch=arch or None,
variant=variant or None,
)
raise ValueError('Invalid platform string "{0}": unknown OS or architecture'.format(string))
raise ValueError(f'Invalid platform string "{string}": unknown OS or architecture')
os = _validate_part(string, parts[0], 'OS')
if not os:
raise ValueError('Invalid platform string "{0}": OS is empty'.format(string))
raise ValueError(f'Invalid platform string "{string}": OS is empty')
arch = _validate_part(string, parts[1], 'architecture') if len(parts) > 1 else None
if arch is not None and not arch:
raise ValueError('Invalid platform string "{0}": architecture is empty'.format(string))
raise ValueError(f'Invalid platform string "{string}": architecture is empty')
variant = _validate_part(string, parts[2], 'variant') if len(parts) > 2 else None
if variant is not None and not variant:
raise ValueError('Invalid platform string "{0}": variant is empty'.format(string))
raise ValueError(f'Invalid platform string "{string}": variant is empty')
arch, variant = _normalize_arch(arch, variant or '')
if len(parts) == 2 and arch == 'arm' and variant == 'v7':
variant = None
@@ -155,7 +155,7 @@ class _Platform(object):
return '/'.join(parts)
def __repr__(self):
return '_Platform(os={os!r}, arch={arch!r}, variant={variant!r})'.format(os=self.os, arch=self.arch, variant=self.variant)
return f'_Platform(os={self.os!r}, arch={self.arch!r}, variant={self.variant!r})'
def __eq__(self, other):
return self.os == other.os and self.arch == other.arch and self.variant == other.variant
+1 -1
View File
@@ -35,7 +35,7 @@ def unscramble(value, key):
'''Do NOT use this for cryptographic purposes!'''
if len(key) < 1:
raise ValueError('Key must be at least one byte')
if not value.startswith(u'=S='):
if not value.startswith('=S='):
raise ValueError('Value does not start with indicator')
value = base64.b64decode(value[3:])
k = key[0]
+60 -61
View File
@@ -125,12 +125,9 @@ def _get_tls_config(fail_function, **kwargs):
if assert_hostname is not None:
fail_function(
"tls_hostname is not compatible with Docker SDK for Python 7.0.0+. You are using"
" Docker SDK for Python {docker_py_version}. The tls_hostname option (value: {tls_hostname})"
f" Docker SDK for Python {docker_version}. The tls_hostname option (value: {assert_hostname})"
" has either been set directly or with the environment variable DOCKER_TLS_HOSTNAME."
" Make sure it is not set, or switch to an older version of Docker SDK for Python.".format(
docker_py_version=docker_version,
tls_hostname=assert_hostname,
)
" Make sure it is not set, or switch to an older version of Docker SDK for Python."
)
# Filter out all None parameters
kwargs = dict((k, v) for k, v in kwargs.items() if v is not None)
@@ -138,7 +135,7 @@ def _get_tls_config(fail_function, **kwargs):
tls_config = TLSConfig(**kwargs)
return tls_config
except TLSParameterError as exc:
fail_function("TLS config error: %s" % exc)
fail_function(f"TLS config error: {exc}")
def is_using_tls(auth):
@@ -203,17 +200,20 @@ class AnsibleDockerClientBase(Client):
self.fail("Cannot have both the docker-py and docker python modules (old and new version of Docker "
"SDK for Python) installed together as they use the same namespace and cause a corrupt "
"installation. Please uninstall both packages, and re-install only the docker-py or docker "
"python module (for %s's Python %s). It is recommended to install the docker module. Please "
f"python module (for {platform.node()}'s Python {sys.executable}). It is recommended to install the docker module. Please "
"note that simply uninstalling one of the modules can leave the other module in a broken "
"state." % (platform.node(), sys.executable))
"state.")
if not HAS_DOCKER_PY:
msg = missing_required_lib("Docker SDK for Python: docker>=5.0.0")
msg = msg + ", for example via `pip install docker`. The error was: %s"
self.fail(msg % HAS_DOCKER_ERROR, exception=HAS_DOCKER_TRACEBACK)
msg = f"{msg}, for example via `pip install docker`. The error was: {HAS_DOCKER_ERROR}"
self.fail(msg, exception=HAS_DOCKER_TRACEBACK)
if self.docker_py_version < LooseVersion(min_docker_version):
msg = "Error: Docker SDK for Python version is %s (%s's Python %s). Minimum version required is %s."
msg = (
f"Error: Docker SDK for Python version is {docker_version} ({platform.node()}'s Python {sys.executable})."
f" Minimum version required is {min_docker_version}."
)
if not NEEDS_DOCKER_PY2:
# The minimal required version is < 2.0 (and the current version as well).
# Advertise docker (instead of docker-py).
@@ -222,7 +222,7 @@ class AnsibleDockerClientBase(Client):
msg += DOCKERPYUPGRADE_SWITCH_TO_DOCKER
else:
msg += DOCKERPYUPGRADE_UPGRADE_DOCKER
self.fail(msg % (docker_version, platform.node(), sys.executable, min_docker_version))
self.fail(msg)
self._connect_params = get_connect_params(self.auth_params, fail_function=self.fail)
@@ -230,14 +230,14 @@ class AnsibleDockerClientBase(Client):
super(AnsibleDockerClientBase, self).__init__(**self._connect_params)
self.docker_api_version_str = self.api_version
except APIError as exc:
self.fail("Docker API error: %s" % exc)
self.fail(f"Docker API error: {exc}")
except Exception as exc:
self.fail("Error connecting: %s" % exc)
self.fail(f"Error connecting: {exc}")
self.docker_api_version = LooseVersion(self.docker_api_version_str)
min_docker_api_version = min_docker_api_version or '1.25'
if self.docker_api_version < LooseVersion(min_docker_api_version):
self.fail('Docker API version is %s. Minimum version required is %s.' % (self.docker_api_version_str, min_docker_api_version))
self.fail(f'Docker API version is {self.docker_api_version_str}. Minimum version required is {min_docker_api_version}.')
def log(self, msg, pretty_print=False):
pass
@@ -331,23 +331,23 @@ class AnsibleDockerClientBase(Client):
def _handle_ssl_error(self, error):
match = re.match(r"hostname.*doesn\'t match (\'.*\')", str(error))
if match:
self.fail("You asked for verification that Docker daemons certificate's hostname matches %s. "
"The actual certificate's hostname is %s. Most likely you need to set DOCKER_TLS_HOSTNAME "
"or pass `tls_hostname` with a value of %s. You may also use TLS without verification by "
"setting the `tls` parameter to true."
% (self.auth_params['tls_hostname'], match.group(1), match.group(1)))
self.fail("SSL Exception: %s" % (error))
hostname = self.auth_params['tls_hostname']
self.fail(f"You asked for verification that Docker daemons certificate's hostname matches {hostname}. "
f"The actual certificate's hostname is {match.group(1)}. Most likely you need to set DOCKER_TLS_HOSTNAME "
f"or pass `tls_hostname` with a value of {match.group(1)}. You may also use TLS without verification by "
"setting the `tls` parameter to true.")
self.fail(f"SSL Exception: {error}")
def get_container_by_id(self, container_id):
try:
self.log("Inspecting container Id %s" % container_id)
self.log(f"Inspecting container Id {container_id}")
result = self.inspect_container(container=container_id)
self.log("Completed container inspection")
return result
except NotFound as dummy:
return None
except Exception as exc:
self.fail("Error inspecting container: %s" % exc)
self.fail(f"Error inspecting container: {exc}")
def get_container(self, name=None):
'''
@@ -363,7 +363,7 @@ class AnsibleDockerClientBase(Client):
result = None
try:
for container in self.containers(all=True):
self.log("testing container: %s" % (container['Names']))
self.log(f"testing container: {container['Names']}")
if isinstance(container['Names'], list) and search_name in container['Names']:
result = container
break
@@ -376,7 +376,7 @@ class AnsibleDockerClientBase(Client):
except SSLError as exc:
self._handle_ssl_error(exc)
except Exception as exc:
self.fail("Error retrieving container list: %s" % exc)
self.fail(f"Error retrieving container list: {exc}")
if result is None:
return None
@@ -395,7 +395,7 @@ class AnsibleDockerClientBase(Client):
if network_id is None:
try:
for network in self.networks():
self.log("testing network: %s" % (network['Name']))
self.log(f"testing network: {network['Name']}")
if name == network['Name']:
result = network
break
@@ -405,20 +405,20 @@ class AnsibleDockerClientBase(Client):
except SSLError as exc:
self._handle_ssl_error(exc)
except Exception as exc:
self.fail("Error retrieving network list: %s" % exc)
self.fail(f"Error retrieving network list: {exc}")
if result is not None:
network_id = result['Id']
if network_id is not None:
try:
self.log("Inspecting network Id %s" % network_id)
self.log(f"Inspecting network Id {network_id}")
result = self.inspect_network(network_id)
self.log("Completed network inspection")
except NotFound as dummy:
return None
except Exception as exc:
self.fail("Error inspecting network: %s" % exc)
self.fail(f"Error inspecting network: {exc}")
return result
@@ -429,7 +429,7 @@ class AnsibleDockerClientBase(Client):
if not name:
return None
self.log("Find image %s:%s" % (name, tag))
self.log(f"Find image {name}:{tag}")
images = self._image_lookup(name, tag)
if not images:
# In API <= 1.20 seeing 'docker.io/<name>' as the name of images pulled from docker hub
@@ -437,41 +437,41 @@ class AnsibleDockerClientBase(Client):
if registry == 'docker.io':
# If docker.io is explicitly there in name, the image
# is not found in some cases (#41509)
self.log("Check for docker.io image: %s" % repo_name)
self.log(f"Check for docker.io image: {repo_name}")
images = self._image_lookup(repo_name, tag)
if not images and repo_name.startswith('library/'):
# Sometimes library/xxx images are not found
lookup = repo_name[len('library/'):]
self.log("Check for docker.io image: %s" % lookup)
self.log(f"Check for docker.io image: {lookup}")
images = self._image_lookup(lookup, tag)
if not images:
# Last case for some Docker versions: if docker.io was not there,
# it can be that the image was not found either
# (https://github.com/ansible/ansible/pull/15586)
lookup = "%s/%s" % (registry, repo_name)
self.log("Check for docker.io image: %s" % lookup)
lookup = f"{registry}/{repo_name}"
self.log(f"Check for docker.io image: {lookup}")
images = self._image_lookup(lookup, tag)
if not images and '/' not in repo_name:
# This seems to be happening with podman-docker
# (https://github.com/ansible-collections/community.docker/issues/291)
lookup = "%s/library/%s" % (registry, repo_name)
self.log("Check for docker.io image: %s" % lookup)
lookup = f"{registry}/library/{repo_name}"
self.log(f"Check for docker.io image: {lookup}")
images = self._image_lookup(lookup, tag)
if len(images) > 1:
self.fail("Daemon returned more than one result for %s:%s" % (name, tag))
self.fail(f"Daemon returned more than one result for {name}:{tag}")
if len(images) == 1:
try:
inspection = self.inspect_image(images[0]['Id'])
except NotFound:
self.log("Image %s:%s not found." % (name, tag))
self.log(f"Image {name}:{tag} not found.")
return None
except Exception as exc:
self.fail("Error inspecting image %s:%s - %s" % (name, tag, str(exc)))
self.fail(f"Error inspecting image {name}:{tag} - {exc}")
return inspection
self.log("Image %s:%s not found." % (name, tag))
self.log(f"Image {name}:{tag} not found.")
return None
def find_image_by_id(self, image_id, accept_missing_image=False):
@@ -481,16 +481,16 @@ class AnsibleDockerClientBase(Client):
if not image_id:
return None
self.log("Find image %s (by ID)" % image_id)
self.log(f"Find image {image_id} (by ID)")
try:
inspection = self.inspect_image(image_id)
except NotFound as exc:
if not accept_missing_image:
self.fail("Error inspecting image ID %s - %s" % (image_id, str(exc)))
self.log("Image %s not found." % image_id)
self.fail(f"Error inspecting image ID {image_id} - {exc}")
self.log(f"Image {image_id} not found.")
return None
except Exception as exc:
self.fail("Error inspecting image ID %s - %s" % (image_id, str(exc)))
self.fail(f"Error inspecting image ID {image_id} - {exc}")
return inspection
def _image_lookup(self, name, tag):
@@ -502,11 +502,11 @@ class AnsibleDockerClientBase(Client):
try:
response = self.images(name=name)
except Exception as exc:
self.fail("Error searching for image %s - %s" % (name, str(exc)))
self.fail(f"Error searching for image {name} - {exc}")
images = response
if tag:
lookup = "%s:%s" % (name, tag)
lookup_digest = "%s@%s" % (name, tag)
lookup = f"{name}:{tag}"
lookup_digest = f"{name}@{tag}"
images = []
for image in response:
tags = image.get('RepoTags')
@@ -527,7 +527,7 @@ class AnsibleDockerClientBase(Client):
)
if platform is not None:
kwargs['platform'] = platform
self.log("Pulling image %s:%s" % (name, tag))
self.log(f"Pulling image {name}:{tag}")
old_tag = self.find_image(name, tag)
try:
for line in self.pull(name, **kwargs):
@@ -535,13 +535,11 @@ class AnsibleDockerClientBase(Client):
if line.get('error'):
if line.get('errorDetail'):
error_detail = line.get('errorDetail')
self.fail("Error pulling %s - code: %s message: %s" % (name,
error_detail.get('code'),
error_detail.get('message')))
self.fail(f"Error pulling {name} - code: {error_detail.get('code')} message: {error_detail.get('message')}")
else:
self.fail("Error pulling %s - %s" % (name, line.get('error')))
self.fail(f"Error pulling {name} - {line.get('error')}")
except Exception as exc:
self.fail("Error pulling image %s:%s - %s" % (name, tag, str(exc)))
self.fail(f"Error pulling image {name}:{tag} - {exc}")
new_tag = self.find_image(name, tag)
@@ -652,22 +650,23 @@ class AnsibleDockerClient(AnsibleDockerClientBase):
if 'usage_msg' in data:
usg = data['usage_msg']
else:
usg = 'set %s option' % (option, )
usg = f'set {option} option'
if not support_docker_api:
msg = 'Docker API version is %s. Minimum version required is %s to %s.'
msg = msg % (self.docker_api_version_str, data['docker_api_version'], usg)
msg = f"Docker API version is {self.docker_api_version_str}. Minimum version required is {data['docker_api_version']} to {usg}."
elif not support_docker_py:
msg = "Docker SDK for Python version is %s (%s's Python %s). Minimum version required is %s to %s. "
msg = (
f"Docker SDK for Python version is {docker_version} ({platform.node()}'s Python {sys.executable})."
f" Minimum version required is {data['docker_py_version']} to {usg}. "
)
if LooseVersion(data['docker_py_version']) < LooseVersion('2.0.0'):
msg += DOCKERPYUPGRADE_RECOMMEND_DOCKER
elif self.docker_py_version < LooseVersion('2.0.0'):
msg += DOCKERPYUPGRADE_SWITCH_TO_DOCKER
else:
msg += DOCKERPYUPGRADE_UPGRADE_DOCKER
msg = msg % (docker_version, platform.node(), sys.executable, data['docker_py_version'], usg)
else:
# should not happen
msg = 'Cannot %s with your configuration.' % (usg, )
msg = f'Cannot {usg} with your configuration.'
self.fail(msg)
def report_warnings(self, result, warnings_key=None):
@@ -691,6 +690,6 @@ class AnsibleDockerClient(AnsibleDockerClientBase):
result = result.get(key)
if isinstance(result, Sequence):
for warning in result:
self.module.warn('Docker warning: {0}'.format(warning))
self.module.warn(f'Docker warning: {warning}')
elif isinstance(result, str) and result:
self.module.warn('Docker warning: {0}'.format(result))
self.module.warn(f'Docker warning: {result}')
+45 -48
View File
@@ -59,7 +59,7 @@ def _get_tls_config(fail_function, **kwargs):
tls_config = TLSConfig(**kwargs)
return tls_config
except TLSParameterError as exc:
fail_function("TLS config error: %s" % exc)
fail_function(f"TLS config error: {exc}")
def is_using_tls(auth_data):
@@ -115,14 +115,14 @@ class AnsibleDockerClientBase(Client):
except MissingRequirementException as exc:
self.fail(missing_required_lib(exc.requirement), exception=exc.import_exception)
except APIError as exc:
self.fail("Docker API error: %s" % exc)
self.fail(f"Docker API error: {exc}")
except Exception as exc:
self.fail("Error connecting: %s" % exc)
self.fail(f"Error connecting: {exc}")
self.docker_api_version = LooseVersion(self.docker_api_version_str)
min_docker_api_version = min_docker_api_version or '1.25'
if self.docker_api_version < LooseVersion(min_docker_api_version):
self.fail('Docker API version is %s. Minimum version required is %s.' % (self.docker_api_version_str, min_docker_api_version))
self.fail(f'Docker API version is {self.docker_api_version_str}. Minimum version required is {min_docker_api_version}.')
def log(self, msg, pretty_print=False):
pass
@@ -219,23 +219,23 @@ class AnsibleDockerClientBase(Client):
def _handle_ssl_error(self, error):
match = re.match(r"hostname.*doesn\'t match (\'.*\')", str(error))
if match:
self.fail("You asked for verification that Docker daemons certificate's hostname matches %s. "
"The actual certificate's hostname is %s. Most likely you need to set DOCKER_TLS_HOSTNAME "
"or pass `tls_hostname` with a value of %s. You may also use TLS without verification by "
"setting the `tls` parameter to true."
% (self.auth_params['tls_hostname'], match.group(1), match.group(1)))
self.fail("SSL Exception: %s" % (error))
hostname = self.auth_params['tls_hostname']
self.fail(f"You asked for verification that Docker daemons certificate's hostname matches {hostname}. "
f"The actual certificate's hostname is {match.group(1)}. Most likely you need to set DOCKER_TLS_HOSTNAME "
f"or pass `tls_hostname` with a value of {match.group(1)}. You may also use TLS without verification by "
"setting the `tls` parameter to true.")
self.fail(f"SSL Exception: {error}")
def get_container_by_id(self, container_id):
try:
self.log("Inspecting container Id %s" % container_id)
self.log(f"Inspecting container Id {container_id}")
result = self.get_json('/containers/{0}/json', container_id)
self.log("Completed container inspection")
return result
except NotFound as dummy:
return None
except Exception as exc:
self.fail("Error inspecting container: %s" % exc)
self.fail(f"Error inspecting container: {exc}")
def get_container(self, name=None):
'''
@@ -258,7 +258,7 @@ class AnsibleDockerClientBase(Client):
}
containers = self.get_json("/containers/json", params=params)
for container in containers:
self.log("testing container: %s" % (container['Names']))
self.log(f"testing container: {container['Names']}")
if isinstance(container['Names'], list) and search_name in container['Names']:
result = container
break
@@ -271,7 +271,7 @@ class AnsibleDockerClientBase(Client):
except SSLError as exc:
self._handle_ssl_error(exc)
except Exception as exc:
self.fail("Error retrieving container list: %s" % exc)
self.fail(f"Error retrieving container list: {exc}")
if result is None:
return None
@@ -291,7 +291,7 @@ class AnsibleDockerClientBase(Client):
try:
networks = self.get_json("/networks")
for network in networks:
self.log("testing network: %s" % (network['Name']))
self.log(f"testing network: {network['Name']}")
if name == network['Name']:
result = network
break
@@ -301,20 +301,20 @@ class AnsibleDockerClientBase(Client):
except SSLError as exc:
self._handle_ssl_error(exc)
except Exception as exc:
self.fail("Error retrieving network list: %s" % exc)
self.fail(f"Error retrieving network list: {exc}")
if result is not None:
network_id = result['Id']
if network_id is not None:
try:
self.log("Inspecting network Id %s" % network_id)
self.log(f"Inspecting network Id {network_id}")
result = self.get_json('/networks/{0}', network_id)
self.log("Completed network inspection")
except NotFound as dummy:
return None
except Exception as exc:
self.fail("Error inspecting network: %s" % exc)
self.fail(f"Error inspecting network: {exc}")
return result
@@ -336,10 +336,10 @@ class AnsibleDockerClientBase(Client):
params['filters'] = convert_filters({'reference': name})
images = self.get_json("/images/json", params=params)
except Exception as exc:
self.fail("Error searching for image %s - %s" % (name, str(exc)))
self.fail(f"Error searching for image {name} - {exc}")
if tag:
lookup = "%s:%s" % (name, tag)
lookup_digest = "%s@%s" % (name, tag)
lookup = f"{name}:{tag}"
lookup_digest = f"{name}@{tag}"
response = images
images = []
for image in response:
@@ -357,7 +357,7 @@ class AnsibleDockerClientBase(Client):
if not name:
return None
self.log("Find image %s:%s" % (name, tag))
self.log(f"Find image {name}:{tag}")
images = self._image_lookup(name, tag)
if not images:
# In API <= 1.20 seeing 'docker.io/<name>' as the name of images pulled from docker hub
@@ -365,40 +365,40 @@ class AnsibleDockerClientBase(Client):
if registry == 'docker.io':
# If docker.io is explicitly there in name, the image
# is not found in some cases (#41509)
self.log("Check for docker.io image: %s" % repo_name)
self.log(f"Check for docker.io image: {repo_name}")
images = self._image_lookup(repo_name, tag)
if not images and repo_name.startswith('library/'):
# Sometimes library/xxx images are not found
lookup = repo_name[len('library/'):]
self.log("Check for docker.io image: %s" % lookup)
self.log(f"Check for docker.io image: {lookup}")
images = self._image_lookup(lookup, tag)
if not images:
# Last case for some Docker versions: if docker.io was not there,
# it can be that the image was not found either
# (https://github.com/ansible/ansible/pull/15586)
lookup = "%s/%s" % (registry, repo_name)
self.log("Check for docker.io image: %s" % lookup)
lookup = f"{registry}/{repo_name}"
self.log(f"Check for docker.io image: {lookup}")
images = self._image_lookup(lookup, tag)
if not images and '/' not in repo_name:
# This seems to be happening with podman-docker
# (https://github.com/ansible-collections/community.docker/issues/291)
lookup = "%s/library/%s" % (registry, repo_name)
self.log("Check for docker.io image: %s" % lookup)
lookup = f"{registry}/library/{repo_name}"
self.log(f"Check for docker.io image: {lookup}")
images = self._image_lookup(lookup, tag)
if len(images) > 1:
self.fail("Daemon returned more than one result for %s:%s" % (name, tag))
self.fail(f"Daemon returned more than one result for {name}:{tag}")
if len(images) == 1:
try:
return self.get_json('/images/{0}/json', images[0]['Id'])
except NotFound:
self.log("Image %s:%s not found." % (name, tag))
self.log(f"Image {name}:{tag} not found.")
return None
except Exception as exc:
self.fail("Error inspecting image %s:%s - %s" % (name, tag, str(exc)))
self.fail(f"Error inspecting image {name}:{tag} - {exc}")
self.log("Image %s:%s not found." % (name, tag))
self.log(f"Image {name}:{tag} not found.")
return None
def find_image_by_id(self, image_id, accept_missing_image=False):
@@ -408,22 +408,22 @@ class AnsibleDockerClientBase(Client):
if not image_id:
return None
self.log("Find image %s (by ID)" % image_id)
self.log(f"Find image {image_id} (by ID)")
try:
return self.get_json('/images/{0}/json', image_id)
except NotFound as exc:
if not accept_missing_image:
self.fail("Error inspecting image ID %s - %s" % (image_id, str(exc)))
self.log("Image %s not found." % image_id)
self.fail(f"Error inspecting image ID {image_id} - {exc}")
self.log(f"Image {image_id} not found.")
return None
except Exception as exc:
self.fail("Error inspecting image ID %s - %s" % (image_id, str(exc)))
self.fail(f"Error inspecting image ID {image_id} - {exc}")
def pull_image(self, name, tag="latest", platform=None):
'''
Pull an image
'''
self.log("Pulling image %s:%s" % (name, tag))
self.log(f"Pulling image {name}:{tag}")
old_tag = self.find_image(name, tag)
try:
repository, image_tag = parse_repository_tag(name)
@@ -450,13 +450,11 @@ class AnsibleDockerClientBase(Client):
if line.get('error'):
if line.get('errorDetail'):
error_detail = line.get('errorDetail')
self.fail("Error pulling %s - code: %s message: %s" % (name,
error_detail.get('code'),
error_detail.get('message')))
self.fail(f"Error pulling {name} - code: {error_detail.get('code')} message: {error_detail.get('message')}")
else:
self.fail("Error pulling %s - %s" % (name, line.get('error')))
self.fail(f"Error pulling {name} - {line.get('error')}")
except Exception as exc:
self.fail("Error pulling image %s:%s - %s" % (name, tag, str(exc)))
self.fail(f"Error pulling image {name}:{tag} - {exc}")
new_tag = self.find_image(name, tag)
@@ -547,13 +545,12 @@ class AnsibleDockerClient(AnsibleDockerClientBase):
if 'usage_msg' in data:
usg = data['usage_msg']
else:
usg = 'set %s option' % (option, )
usg = f'set {option} option'
if not support_docker_api:
msg = 'Docker API version is %s. Minimum version required is %s to %s.'
msg = msg % (self.docker_api_version_str, data['docker_api_version'], usg)
msg = f"Docker API version is {self.docker_api_version_str}. Minimum version required is {data['docker_api_version']} to {usg}."
else:
# should not happen
msg = 'Cannot %s with your configuration.' % (usg, )
msg = f'Cannot {usg} with your configuration.'
self.fail(msg)
def report_warnings(self, result, warnings_key=None):
@@ -577,6 +574,6 @@ class AnsibleDockerClient(AnsibleDockerClientBase):
result = result.get(key)
if isinstance(result, Sequence):
for warning in result:
self.module.warn('Docker warning: {0}'.format(warning))
self.module.warn(f'Docker warning: {warning}')
elif isinstance(result, str) and result:
self.module.warn('Docker warning: {0}'.format(result))
self.module.warn(f'Docker warning: {result}')
+21 -29
View File
@@ -90,7 +90,7 @@ class AnsibleDockerClientBase(object):
self.docker_api_version = LooseVersion(self.docker_api_version_str)
min_docker_api_version = min_docker_api_version or '1.25'
if self.docker_api_version < LooseVersion(min_docker_api_version):
self.fail('Docker API version is %s. Minimum version required is %s.' % (self.docker_api_version_str, min_docker_api_version))
self.fail(f'Docker API version is {self.docker_api_version_str}. Minimum version required is {min_docker_api_version}.')
else:
self.docker_api_version_str = None
self.docker_api_version = None
@@ -128,11 +128,7 @@ class AnsibleDockerClientBase(object):
try:
data = json.loads(stdout)
except Exception as exc:
self.fail('Error while parsing JSON output of {cmd}: {exc}\nJSON output: {stdout}'.format(
cmd=self._compose_cmd_str(args),
exc=to_native(exc),
stdout=to_native(stdout),
))
self.fail(f'Error while parsing JSON output of {self._compose_cmd_str(args)}: {exc}\nJSON output: {to_native(stdout)}')
return rc, data, stderr
# def call_cli_json_stream(self, *args, check_rc=False, data=None, cwd=None, environ_update=None, warn_on_stderr=False):
@@ -148,11 +144,7 @@ class AnsibleDockerClientBase(object):
if line.startswith(b'{'):
result.append(json.loads(line))
except Exception as exc:
self.fail('Error while parsing JSON output of {cmd}: {exc}\nJSON output: {stdout}'.format(
cmd=self._compose_cmd_str(args),
exc=to_native(exc),
stdout=to_native(stdout),
))
self.fail(f'Error while parsing JSON output of {self._compose_cmd_str(args)}: {exc}\nJSON output: {to_native(stdout)}')
return rc, result, stderr
@abc.abstractmethod
@@ -188,12 +180,12 @@ class AnsibleDockerClientBase(object):
if the tag exists.
'''
dummy, images, dummy = self.call_cli_json_stream(
'image', 'ls', '--format', '{{ json . }}', '--no-trunc', '--filter', 'reference={0}'.format(name),
'image', 'ls', '--format', '{{ json . }}', '--no-trunc', '--filter', f'reference={name}',
check_rc=True,
)
if tag:
lookup = "%s:%s" % (name, tag)
lookup_digest = "%s@%s" % (name, tag)
lookup = f"{name}:{tag}"
lookup_digest = f"{name}@{tag}"
response = images
images = []
for image in response:
@@ -209,7 +201,7 @@ class AnsibleDockerClientBase(object):
if not name:
return None
self.log("Find image %s:%s" % (name, tag))
self.log(f"Find image {name}:{tag}")
images = self._image_lookup(name, tag)
if not images:
# In API <= 1.20 seeing 'docker.io/<name>' as the name of images pulled from docker hub
@@ -217,40 +209,40 @@ class AnsibleDockerClientBase(object):
if registry == 'docker.io':
# If docker.io is explicitly there in name, the image
# is not found in some cases (#41509)
self.log("Check for docker.io image: %s" % repo_name)
self.log(f"Check for docker.io image: {repo_name}")
images = self._image_lookup(repo_name, tag)
if not images and repo_name.startswith('library/'):
# Sometimes library/xxx images are not found
lookup = repo_name[len('library/'):]
self.log("Check for docker.io image: %s" % lookup)
self.log(f"Check for docker.io image: {lookup}")
images = self._image_lookup(lookup, tag)
if not images:
# Last case for some Docker versions: if docker.io was not there,
# it can be that the image was not found either
# (https://github.com/ansible/ansible/pull/15586)
lookup = "%s/%s" % (registry, repo_name)
self.log("Check for docker.io image: %s" % lookup)
lookup = f"{registry}/{repo_name}"
self.log(f"Check for docker.io image: {lookup}")
images = self._image_lookup(lookup, tag)
if not images and '/' not in repo_name:
# This seems to be happening with podman-docker
# (https://github.com/ansible-collections/community.docker/issues/291)
lookup = "%s/library/%s" % (registry, repo_name)
self.log("Check for docker.io image: %s" % lookup)
lookup = f"{registry}/library/{repo_name}"
self.log(f"Check for docker.io image: {lookup}")
images = self._image_lookup(lookup, tag)
if len(images) > 1:
self.fail("Daemon returned more than one result for %s:%s" % (name, tag))
self.fail(f"Daemon returned more than one result for {name}:{tag}")
if len(images) == 1:
rc, image, stderr = self.call_cli_json('image', 'inspect', images[0]['ID'])
if not image:
self.log("Image %s:%s not found." % (name, tag))
self.log(f"Image {name}:{tag} not found.")
return None
if rc != 0:
self.fail("Error inspecting image %s:%s - %s" % (name, tag, to_native(stderr)))
self.fail(f"Error inspecting image {name}:{tag} - {to_native(stderr)}")
return image[0]
self.log("Image %s:%s not found." % (name, tag))
self.log(f"Image {name}:{tag} not found.")
return None
def find_image_by_id(self, image_id, accept_missing_image=False):
@@ -260,15 +252,15 @@ class AnsibleDockerClientBase(object):
if not image_id:
return None
self.log("Find image %s (by ID)" % image_id)
self.log(f"Find image {image_id} (by ID)")
rc, image, stderr = self.call_cli_json('image', 'inspect', image_id)
if not image:
if not accept_missing_image:
self.fail("Error inspecting image ID %s - %s" % (image_id, to_native(stderr)))
self.log("Image %s not found." % image_id)
self.fail(f"Error inspecting image ID {image_id} - {to_native(stderr)}")
self.log(f"Image {image_id} not found.")
return None
if rc != 0:
self.fail("Error inspecting image ID %s - %s" % (image_id, to_native(stderr)))
self.fail(f"Error inspecting image ID {image_id} - {to_native(stderr)}")
return image[0]
+22 -41
View File
@@ -271,10 +271,10 @@ def _extract_event(line, warn_function=None):
if match:
if warn_function:
if match.group('msg'):
msg = '{rid}: {msg}'
msg = f"{match.group('resource_id')}: {match.group('msg')}"
else:
msg = 'Unspecified warning for {rid}'
warn_function(msg.format(rid=match.group('resource_id'), msg=match.group('msg')))
msg = f"Unspecified warning for {match.group('resource_id')}"
warn_function(msg)
return None, True
match = _RE_PULL_PROGRESS.match(line)
if match:
@@ -323,9 +323,8 @@ def _warn_missing_dry_run_prefix(line, warn_missing_dry_run_prefix, warn_functio
# This could be a bug, a change of docker compose's output format, ...
# Tell the user to report it to us :-)
warn_function(
'Event line is missing dry-run mode marker: {0!r}. Please report this at '
f'Event line is missing dry-run mode marker: {line!r}. Please report this at '
'https://github.com/ansible-collections/community.docker/issues/new?assignees=&labels=&projects=&template=bug_report.md'
.format(line)
)
@@ -334,9 +333,8 @@ def _warn_unparsable_line(line, warn_function):
# Tell the user to report it to us :-)
if warn_function:
warn_function(
'Cannot parse event from line: {0!r}. Please report this at '
f'Cannot parse event from line: {line!r}. Please report this at '
'https://github.com/ansible-collections/community.docker/issues/new?assignees=&labels=&projects=&template=bug_report.md'
.format(line)
)
@@ -382,9 +380,8 @@ def parse_json_events(stderr, warn_function=None):
continue
if warn_function:
warn_function(
'Cannot parse event from non-JSON line: {0!r}. Please report this at '
f'Cannot parse event from non-JSON line: {line!r}. Please report this at '
'https://github.com/ansible-collections/community.docker/issues/new?assignees=&labels=&projects=&template=bug_report.md'
.format(line)
)
continue
try:
@@ -392,9 +389,8 @@ def parse_json_events(stderr, warn_function=None):
except Exception as exc:
if warn_function:
warn_function(
'Cannot parse event from line: {0!r}: {1}. Please report this at '
f'Cannot parse event from line: {line!r}: {exc}. Please report this at '
'https://github.com/ansible-collections/community.docker/issues/new?assignees=&labels=&projects=&template=bug_report.md'
.format(line, exc)
)
continue
if line_data.get('tail'):
@@ -449,9 +445,8 @@ def parse_json_events(stderr, warn_function=None):
except KeyError:
if warn_function:
warn_function(
'Unknown resource type {0!r} in line {1!r}. Please report this at '
f'Unknown resource type {resource_type_str!r} in line {line!r}. Please report this at '
'https://github.com/ansible-collections/community.docker/issues/new?assignees=&labels=&projects=&template=bug_report.md'
.format(resource_type_str, line)
)
resource_type = ResourceType.UNKNOWN
elif text in DOCKER_STATUS_PULL:
@@ -589,11 +584,7 @@ def emit_warnings(events, warn_function):
for event in events:
# If a message is present, assume it is a warning
if (event.status is None and event.msg is not None) or event.status in DOCKER_STATUS_WARNING:
warn_function('Docker compose: {resource_type} {resource_id}: {msg}'.format(
resource_type=event.resource_type,
resource_id=event.resource_id,
msg=event.msg,
))
warn_function(f'Docker compose: {event.resource_type} {event.resource_id}: {event.msg}')
def is_failed(events, rc):
@@ -610,22 +601,17 @@ def update_failed(result, events, args, stdout, stderr, rc, cli):
if event.status in DOCKER_STATUS_ERROR:
if event.resource_id is None:
if event.resource_type == 'unknown':
msg = 'General error: ' if event.resource_type == 'unknown' else 'Error when processing {resource_type}: '
msg = 'General error: ' if event.resource_type == 'unknown' else f'Error when processing {event.resource_type}: '
else:
msg = 'Error when processing {resource_type} {resource_id}: '
msg = f'Error when processing {event.resource_type} {event.resource_id}: '
if event.resource_type == 'unknown':
msg = 'Error when processing {resource_id}: '
msg = f'Error when processing {event.resource_id}: '
if event.resource_id == '':
msg = 'General error: '
msg += '{status}' if event.msg is None else '{msg}'
errors.append(msg.format(
resource_type=event.resource_type,
resource_id=event.resource_id,
status=event.status,
msg=event.msg,
))
msg += f'{event.status}' if event.msg is None else f'{event.msg}'
errors.append(msg)
if not errors:
errors.append('Return code {code} is non-zero'.format(code=rc))
errors.append(f'Return code {rc} is non-zero')
result['failed'] = True
result['msg'] = '\n'.join(errors)
result['cmd'] = ' '.join(quote(arg) for arg in [cli] + args)
@@ -695,7 +681,7 @@ class BaseComposeManager(DockerBaseClass):
with open(compose_file, 'wb') as f:
yaml.dump(parameters['definition'], f, encoding="utf-8", Dumper=_SafeDumper)
except Exception as exc:
self.fail("Error writing to %s - %s" % (compose_file, to_native(exc)))
self.fail(f"Error writing to {compose_file} - {exc}")
else:
self.project_src = os.path.abspath(parameters['project_src'])
@@ -706,24 +692,20 @@ class BaseComposeManager(DockerBaseClass):
compose_version = self.get_compose_version()
self.compose_version = LooseVersion(compose_version)
if self.compose_version < LooseVersion(min_version):
self.fail('Docker CLI {cli} has the compose plugin with version {version}; need version {min_version} or later'.format(
cli=self.client.get_cli(),
version=compose_version,
min_version=min_version,
))
self.fail(f'Docker CLI {self.client.get_cli()} has the compose plugin with version {compose_version}; need version {min_version} or later')
if not os.path.isdir(self.project_src):
self.fail('"{0}" is not a directory'.format(self.project_src))
self.fail(f'"{self.project_src}" is not a directory')
self.check_files_existing = parameters['check_files_existing']
if self.files:
for file in self.files:
path = os.path.join(self.project_src, file)
if not os.path.exists(path):
self.fail('Cannot find Compose file "{0}" relative to project directory "{1}"'.format(file, self.project_src))
self.fail(f'Cannot find Compose file "{file}" relative to project directory "{self.project_src}"')
elif self.check_files_existing and all(not os.path.exists(os.path.join(self.project_src, f)) for f in DOCKER_COMPOSE_FILES):
filenames = ', '.join(DOCKER_COMPOSE_FILES[:-1])
self.fail('"{0}" does not contain {1}, or {2}'.format(self.project_src, filenames, DOCKER_COMPOSE_FILES[-1]))
self.fail(f'"{self.project_src}" does not contain {filenames}, or {DOCKER_COMPOSE_FILES[-1]}')
# Support for JSON output was added in Compose 2.29.0 (https://github.com/docker/compose/releases/tag/v2.29.0);
# more precisely in https://github.com/docker/compose/pull/11478
@@ -747,12 +729,11 @@ class BaseComposeManager(DockerBaseClass):
def get_compose_version_from_api(self):
compose = self.client.get_client_plugin_info('compose')
if compose is None:
self.fail('Docker CLI {0} does not have the compose plugin installed'.format(self.client.get_cli()))
self.fail(f'Docker CLI {self.client.get_cli()} does not have the compose plugin installed')
if compose['Version'] == 'dev':
self.fail(
'Docker CLI {0} has a compose plugin installed, but it reports version "dev".'
f'Docker CLI {self.client.get_cli()} has a compose plugin installed, but it reports version "dev".'
' Please use a version of the plugin that returns a proper version.'
.format(self.client.get_cli())
)
return compose['Version'].lstrip('v')
+21 -25
View File
@@ -156,7 +156,7 @@ def put_file(client, container, in_path, out_path, user_id, group_id, mode=None,
"""Transfer a file from local to Docker container."""
if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')):
raise DockerFileNotFound(
"file or module does not exist: %s" % to_native(in_path))
f"file or module does not exist: {to_native(in_path)}")
b_in_path = to_bytes(in_path, errors='surrogate_or_strict')
@@ -172,13 +172,13 @@ def put_file(client, container, in_path, out_path, user_id, group_id, mode=None,
elif stat.S_ISLNK(file_stat.st_mode):
stream = _symlink_tar_generator(b_in_path, file_stat, out_file, user_id, group_id, mode=mode, user_name=user_name)
else:
file_part = ' referenced by' if follow_links else ''
raise DockerFileCopyError(
'File{0} {1} is neither a regular file nor a symlink (stat mode {2}).'.format(
' referenced by' if follow_links else '', in_path, oct(file_stat.st_mode)))
f'File{file_part} {in_path} is neither a regular file nor a symlink (stat mode {oct(file_stat.st_mode)}).')
ok = _put_archive(client, container, out_dir, stream)
if not ok:
raise DockerUnexpectedError('Unknown error while creating file "{0}" in container "{1}".'.format(out_path, container))
raise DockerUnexpectedError(f'Unknown error while creating file "{out_path}" in container "{container}".')
def put_file_content(client, container, content, out_path, user_id, group_id, mode, user_name=None):
@@ -189,7 +189,7 @@ def put_file_content(client, container, content, out_path, user_id, group_id, mo
ok = _put_archive(client, container, out_dir, stream)
if not ok:
raise DockerUnexpectedError('Unknown error while creating file "{0}" in container "{1}".'.format(out_path, container))
raise DockerUnexpectedError(f'Unknown error while creating file "{out_path}" in container "{container}".')
def stat_file(client, container, in_path, follow_links=False, log=None):
@@ -208,11 +208,11 @@ def stat_file(client, container, in_path, follow_links=False, log=None):
while True:
if in_path in considered_in_paths:
raise DockerFileCopyError('Found infinite symbolic link loop when trying to stating "{0}"'.format(in_path))
raise DockerFileCopyError(f'Found infinite symbolic link loop when trying to stating "{in_path}"')
considered_in_paths.add(in_path)
if log:
log('FETCH: Stating "%s"' % in_path)
log(f'FETCH: Stating "{in_path}"')
response = client._head(
client._url('/containers/{0}/archive', container),
@@ -226,8 +226,7 @@ def stat_file(client, container, in_path, follow_links=False, log=None):
stat_data = json.loads(base64.b64decode(header))
except Exception as exc:
raise DockerUnexpectedError(
'When retrieving information for {in_path} from {container}, obtained header {header!r} that cannot be loaded as JSON: {exc}'
.format(in_path=in_path, container=container, header=header, exc=exc)
f'When retrieving information for {in_path} from {container}, obtained header {header!r} that cannot be loaded as JSON: {exc}'
)
# https://pkg.go.dev/io/fs#FileMode: bit 32 - 5 means ModeSymlink
@@ -285,11 +284,11 @@ def fetch_file_ex(client, container, in_path, process_none, process_regular, pro
while True:
if in_path in considered_in_paths:
raise DockerFileCopyError('Found infinite symbolic link loop when trying to fetch "{0}"'.format(in_path))
raise DockerFileCopyError(f'Found infinite symbolic link loop when trying to fetch "{in_path}"')
considered_in_paths.add(in_path)
if log:
log('FETCH: Fetching "%s"' % in_path)
log(f'FETCH: Fetching "{in_path}"')
try:
stream = client.get_raw_stream(
'/containers/{0}/archive', container,
@@ -319,7 +318,7 @@ def fetch_file_ex(client, container, in_path, process_none, process_regular, pro
return process_symlink(in_path, symlink_member)
in_path = os.path.join(os.path.split(in_path)[0], symlink_member.linkname)
if log:
log('FETCH: Following symbolic link to "%s"' % in_path)
log(f'FETCH: Following symbolic link to "{in_path}"')
continue
if found:
return result
@@ -331,8 +330,7 @@ def fetch_file(client, container, in_path, out_path, follow_links=False, log=Non
def process_none(in_path):
raise DockerFileNotFound(
'File {in_path} does not exist in container {container}'
.format(in_path=in_path, container=container)
f'File {in_path} does not exist in container {container}'
)
def process_regular(in_path, tar, member):
@@ -352,14 +350,14 @@ def fetch_file(client, container, in_path, out_path, follow_links=False, log=Non
return in_path
def process_other(in_path, member):
raise DockerFileCopyError('Remote file "%s" is not a regular file or a symbolic link' % in_path)
raise DockerFileCopyError(f'Remote file "{in_path}" is not a regular file or a symbolic link')
return fetch_file_ex(client, container, in_path, process_none, process_regular, process_symlink, process_other, follow_links=follow_links, log=log)
def _execute_command(client, container, command, log=None, check_rc=False):
if log:
log('Executing {command} in {container}'.format(command=command, container=container))
log(f'Executing {command} in {container}')
data = {
'Container': container,
@@ -378,10 +376,10 @@ def _execute_command(client, container, command, log=None, check_rc=False):
try:
exec_data = client.post_json_to_json('/containers/{0}/exec', container, data=data)
except NotFound as e:
raise DockerFileCopyError('Could not find container "{container}"'.format(container=container)) from e
raise DockerFileCopyError(f'Could not find container "{container}"') from e
except APIError as e:
if e.response is not None and e.response.status_code == 409:
raise DockerFileCopyError('Cannot execute command in paused container "{container}"'.format(container=container)) from e
raise DockerFileCopyError(f'Cannot execute command in paused container "{container}"') from e
raise
exec_id = exec_data['Id']
@@ -398,12 +396,12 @@ def _execute_command(client, container, command, log=None, check_rc=False):
stderr = stderr or b''
if log:
log('Exit code {rc}, stdout {stdout!r}, stderr {stderr!r}'.format(rc=rc, stdout=stdout, stderr=stderr))
log(f'Exit code {rc}, stdout {stdout!r}, stderr {stderr!r}')
if check_rc and rc != 0:
command_str = ' '.join(command)
raise DockerUnexpectedError(
'Obtained unexpected exit code {rc} when running "{command}" in {container}.\nSTDOUT: {stdout}\nSTDERR: {stderr}'
.format(command=' '.join(command), container=container, rc=rc, stdout=stdout, stderr=stderr)
f'Obtained unexpected exit code {rc} when running "{command_str}" in {container}.\nSTDOUT: {stdout}\nSTDERR: {stderr}'
)
return rc, stdout, stderr
@@ -415,8 +413,7 @@ def determine_user_group(client, container, log=None):
stdout_lines = stdout.splitlines()
if len(stdout_lines) != 2:
raise DockerUnexpectedError(
'Expected two-line output to obtain user and group ID for container {container}, but got {lc} lines:\n{stdout}'
.format(container=container, lc=len(stdout_lines), stdout=stdout)
f'Expected two-line output to obtain user and group ID for container {container}, but got {len(stdout_lines)} lines:\n{stdout}'
)
user_id, group_id = stdout_lines
@@ -424,6 +421,5 @@ def determine_user_group(client, container, log=None):
return int(user_id), int(group_id)
except ValueError:
raise DockerUnexpectedError(
'Expected two-line output with numeric IDs to obtain user and group ID for container {container}, but got "{l1}" and "{l2}" instead'
.format(container=container, l1=user_id, l2=group_id)
f'Expected two-line output with numeric IDs to obtain user and group ID for container {container}, but got "{user_id}" and "{group_id}" instead'
)
+8 -10
View File
@@ -8,8 +8,6 @@ import json
import os
import tarfile
from ansible.module_utils.common.text.converters import to_native
class ImageArchiveManifestSummary(object):
'''
@@ -45,7 +43,7 @@ def api_image_id(archive_image_id):
:rtype: str
'''
return 'sha256:%s' % archive_image_id
return f'sha256:{archive_image_id}'
def load_archived_image_manifest(archive_path):
@@ -79,7 +77,7 @@ def load_archived_image_manifest(archive_path):
manifest = json.load(ef)
except Exception as exc:
raise ImageArchiveInvalidException(
"Failed to decode and deserialize manifest.json: %s" % to_native(exc)
f"Failed to decode and deserialize manifest.json: {exc}"
) from exc
if len(manifest) == 0:
@@ -93,7 +91,7 @@ def load_archived_image_manifest(archive_path):
config_file = meta['Config']
except KeyError as exc:
raise ImageArchiveInvalidException(
"Failed to get Config entry from {0}th manifest in manifest.json: {1}".format(index + 1, to_native(exc))
f"Failed to get Config entry from {index + 1}th manifest in manifest.json: {exc}"
) from exc
# Extracts hash without 'sha256:' prefix
@@ -102,7 +100,7 @@ def load_archived_image_manifest(archive_path):
image_id = os.path.splitext(config_file)[0]
except Exception as exc:
raise ImageArchiveInvalidException(
"Failed to extract image id from config file name %s: %s" % (config_file, to_native(exc))
f"Failed to extract image id from config file name {config_file}: {exc}"
) from exc
for prefix in (
@@ -115,7 +113,7 @@ def load_archived_image_manifest(archive_path):
repo_tags = meta['RepoTags']
except KeyError as exc:
raise ImageArchiveInvalidException(
"Failed to get RepoTags entry from {0}th manifest in manifest.json: {1}".format(index + 1, to_native(exc))
f"Failed to get RepoTags entry from {index + 1}th manifest in manifest.json: {exc}"
) from exc
result.append(ImageArchiveManifestSummary(
@@ -128,13 +126,13 @@ def load_archived_image_manifest(archive_path):
raise
except Exception as exc:
raise ImageArchiveInvalidException(
"Failed to extract manifest.json from tar file %s: %s" % (archive_path, to_native(exc))
f"Failed to extract manifest.json from tar file {archive_path}: {exc}"
) from exc
except ImageArchiveInvalidException:
raise
except Exception as exc:
raise ImageArchiveInvalidException("Failed to open tar file %s: %s" % (archive_path, to_native(exc))) from exc
raise ImageArchiveInvalidException(f"Failed to open tar file {archive_path}: {exc}") from exc
def archived_image_manifest(archive_path):
@@ -162,5 +160,5 @@ def archived_image_manifest(archive_path):
if len(results) == 1:
return results[0]
raise ImageArchiveInvalidException(
"Expected to have one entry in manifest.json but found %s" % len(results)
f"Expected to have one entry in manifest.json but found {len(results)}"
)
+33 -37
View File
@@ -12,7 +12,7 @@ import shlex
from functools import partial
from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.common.text.formatters import human_to_bytes
from ansible_collections.community.docker.plugins.module_utils.util import (
@@ -56,7 +56,7 @@ def _get_ansible_type(type):
if type == 'set':
return 'list'
if type not in ('list', 'dict', 'bool', 'int', 'float', 'str'):
raise Exception('Invalid type "%s"' % (type, ))
raise Exception(f'Invalid type "{type}"')
return type
@@ -365,15 +365,15 @@ def _parse_port_range(range_or_port, module):
try:
start, end = [int(port) for port in range_or_port.split('-')]
except Exception:
module.fail_json(msg='Invalid port range: "{0}"'.format(range_or_port))
module.fail_json(msg=f'Invalid port range: "{range_or_port}"')
if end < start:
module.fail_json(msg='Invalid port range: "{0}"'.format(range_or_port))
module.fail_json(msg=f'Invalid port range: "{range_or_port}"')
return list(range(start, end + 1))
else:
try:
return [int(range_or_port)]
except Exception:
module.fail_json(msg='Invalid port: "{0}"'.format(range_or_port))
module.fail_json(msg=f'Invalid port: "{range_or_port}"')
def _split_colon_ipv6(text, module):
@@ -391,7 +391,7 @@ def _split_colon_ipv6(text, module):
break
j = text.find(']', i)
if j < 0:
module.fail_json(msg='Cannot find closing "]" in input "{0}" for opening "[" at index {1}!'.format(text, i + 1))
module.fail_json(msg=f'Cannot find closing "]" in input "{text}" for opening "[" at index {i + 1}!')
result.extend(text[start:i].split(':'))
k = text.find(':', j)
if k < 0:
@@ -461,11 +461,11 @@ def _preprocess_env(module, values):
for name, value in values['env'].items():
if not isinstance(value, str):
module.fail_json(msg='Non-string value found for env option. Ambiguous env options must be '
'wrapped in quotes to avoid them being interpreted. Key: %s' % (name, ))
f'wrapped in quotes to avoid them being interpreted. Key: {name}')
final_env[name] = to_text(value, errors='surrogate_or_strict')
formatted_env = []
for key, value in final_env.items():
formatted_env.append('%s=%s' % (key, value))
formatted_env.append(f'{key}={value}')
return {
'env': formatted_env,
}
@@ -491,7 +491,7 @@ def _preprocess_convert_to_bytes(module, values, name, unlimited_value=None):
values[name] = value
return values
except ValueError as exc:
module.fail_json(msg='Failed to convert %s to bytes: %s' % (name, to_native(exc)))
module.fail_json(msg=f'Failed to convert {name} to bytes: {exc}')
def _preprocess_mac_address(module, values):
@@ -571,9 +571,9 @@ def _preprocess_mounts(module, values):
def check_collision(t, name):
if t in last:
if name == last[t]:
module.fail_json(msg='The mount point "{0}" appears twice in the {1} option'.format(t, name))
module.fail_json(msg=f'The mount point "{t}" appears twice in the {name} option')
else:
module.fail_json(msg='The mount point "{0}" appears both in the {1} and {2} option'.format(t, name, last[t]))
module.fail_json(msg=f'The mount point "{t}" appears both in the {name} and {last[t]} option')
last[t] = name
if 'mounts' in values:
@@ -588,17 +588,13 @@ def _preprocess_mounts(module, values):
# Sanity checks
if mount['source'] is None and mount_type not in ('tmpfs', 'volume', 'image', 'cluster'):
module.fail_json(msg='source must be specified for mount "{0}" of type "{1}"'.format(target, mount_type))
module.fail_json(msg=f'source must be specified for mount "{target}" of type "{mount_type}"')
for option, req_mount_types in _MOUNT_OPTION_TYPES.items():
if mount[option] is not None and mount_type not in req_mount_types:
type_plural = "" if len(req_mount_types) == 1 else "s"
type_list = '", "'.join(req_mount_types)
module.fail_json(
msg='{0} cannot be specified for mount "{1}" of type "{2}" (needs type{3} "{4}")'.format(
option,
target,
mount_type,
"" if len(req_mount_types) == 1 else "s",
'", "'.join(req_mount_types),
)
msg=f'{option} cannot be specified for mount "{target}" of type "{mount_type}" (needs type{type_plural} "{type_list}")'
)
# Streamline options
@@ -611,22 +607,22 @@ def _preprocess_mounts(module, values):
try:
mount_dict['tmpfs_size'] = human_to_bytes(mount_dict['tmpfs_size'])
except ValueError as exc:
module.fail_json(msg='Failed to convert tmpfs_size of mount "{0}" to bytes: {1}'.format(target, to_native(exc)))
module.fail_json(msg=f'Failed to convert tmpfs_size of mount "{target}" to bytes: {exc}')
if mount_dict['tmpfs_mode'] is not None:
try:
mount_dict['tmpfs_mode'] = int(mount_dict['tmpfs_mode'], 8)
except Exception as dummy:
module.fail_json(msg='tmp_fs mode of mount "{0}" is not an octal string!'.format(target))
module.fail_json(msg=f'tmp_fs mode of mount "{target}" is not an octal string!')
if mount_dict['tmpfs_options']:
opts = []
for idx, opt in enumerate(mount_dict['tmpfs_options']):
if len(opt) != 1:
module.fail_json(msg='tmpfs_options[{1}] of mount "{0}" must be a one-element dictionary!'.format(target, idx + 1))
module.fail_json(msg=f'tmpfs_options[{idx + 1}] of mount "{target}" must be a one-element dictionary!')
k, v = list(opt.items())[0]
if not isinstance(k, str):
module.fail_json(msg='key {2!r} in tmpfs_options[{1}] of mount "{0}" must be a string!'.format(target, idx + 1, k))
module.fail_json(msg=f'key {k!r} in tmpfs_options[{idx + 1}] of mount "{target}" must be a string!')
if v is not None and not isinstance(v, str):
module.fail_json(msg='value {2!r} in tmpfs_options[{1}] of mount "{0}" must be a string or null/none!'.format(target, idx + 1, v))
module.fail_json(msg=f'value {v!r} in tmpfs_options[{idx + 1}] of mount "{target}" must be a string or null/none!')
opts.append([k, v] if v is not None else [k])
mount_dict['tmpfs_options'] = opts
@@ -641,17 +637,17 @@ def _preprocess_mounts(module, values):
if len(parts) == 3:
host, container, mode = parts
if not _is_volume_permissions(mode):
module.fail_json(msg='Found invalid volumes mode: {0}'.format(mode))
module.fail_json(msg=f'Found invalid volumes mode: {mode}')
if re.match(r'[.~]', host):
host = os.path.abspath(os.path.expanduser(host))
check_collision(container, 'volumes')
new_vols.append("%s:%s:%s" % (host, container, mode))
new_vols.append(f"{host}:{container}:{mode}")
continue
elif len(parts) == 2:
if not _is_volume_permissions(parts[1]) and re.match(r'[.~]', parts[0]):
host = os.path.abspath(os.path.expanduser(parts[0]))
check_collision(parts[1], 'volumes')
new_vols.append("%s:%s:rw" % (host, parts[1]))
new_vols.append(f"{host}:{parts[1]}:rw")
continue
check_collision(parts[min(1, len(parts) - 1)], 'volumes')
new_vols.append(vol)
@@ -664,12 +660,12 @@ def _preprocess_mounts(module, values):
if len(parts) == 3:
host, container, mode = parts
if not _is_volume_permissions(mode):
module.fail_json(msg='Found invalid volumes mode: {0}'.format(mode))
module.fail_json(msg=f'Found invalid volumes mode: {mode}')
elif len(parts) == 2:
if not _is_volume_permissions(parts[1]):
host, container, mode = (parts + ['rw'])
if host is not None:
new_binds.append('%s:%s:%s' % (host, container, mode))
new_binds.append(f'{host}:{container}:{mode}')
values['volume_binds'] = new_binds
return values
@@ -694,12 +690,12 @@ def _preprocess_log(module, values):
options = {}
for k, v in values['log_options'].items():
if not isinstance(v, str):
value = to_text(v, errors='surrogate_or_strict')
module.warn(
"Non-string value found for log_options option '%s'. The value is automatically converted to '%s'. "
"If this is not correct, or you want to avoid such warnings, please quote the value." % (
k, to_text(v, errors='surrogate_or_strict'))
f"Non-string value found for log_options option '{k}'. The value is automatically converted to {value!r}. "
"If this is not correct, or you want to avoid such warnings, please quote the value."
)
v = to_text(v, errors='surrogate_or_strict')
v = value
options[k] = v
result['log_options'] = options
return result
@@ -735,7 +731,7 @@ def _preprocess_ports(module, values):
if not re.match(r'^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$', parts[0]) and not re.match(r'^\[[0-9a-fA-F:]+(?:|%[^\]/]+)\]$', ipaddr):
module.fail_json(
msg='Bind addresses for published ports must be IPv4 or IPv6 addresses, not hostnames. '
'Use the dig lookup to resolve hostnames. (Found hostname: {0})'.format(ipaddr)
f'Use the dig lookup to resolve hostnames. (Found hostname: {ipaddr})'
)
if re.match(r'^\[[0-9a-fA-F:]+\]$', ipaddr):
ipaddr = ipaddr[1:-1]
@@ -748,12 +744,12 @@ def _preprocess_ports(module, values):
port_binds = len(container_ports) * [(ipaddr,)]
else:
module.fail_json(
msg='Invalid port description "%s" - expected 1 to 3 colon-separated parts, but got %d. '
'Maybe you forgot to use square brackets ([...]) around an IPv6 address?' % (port, p_len)
msg=f'Invalid port description "{port}" - expected 1 to 3 colon-separated parts, but got {p_len}. '
'Maybe you forgot to use square brackets ([...]) around an IPv6 address?'
)
for bind, container_port in zip(port_binds, container_ports):
idx = '{0}/{1}'.format(container_port, protocol) if protocol else container_port
idx = f'{container_port}/{protocol}' if protocol else container_port
if idx in binds:
old_bind = binds[idx]
if isinstance(old_bind, list):
@@ -8,7 +8,7 @@ from __future__ import annotations
import json
import traceback
from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.common.text.formatters import human_to_bytes
from ansible_collections.community.docker.plugins.module_utils.common_api import (
@@ -123,7 +123,7 @@ def _get_ansible_type(type):
if type == 'set':
return 'list'
if type not in ('list', 'dict', 'bool', 'int', 'float', 'str'):
raise Exception('Invalid type "%s"' % (type, ))
raise Exception(f'Invalid type "{type}"')
return type
@@ -248,8 +248,9 @@ class DockerAPIEngineDriver(EngineDriver):
value = normalize_links(value)
params[dest_para] = value
if parameters:
ups = ', '.join([f'"{p}"' for p in sorted(parameters)])
raise Exception(
'Unknown parameter(s) for connect_container_to_network for Docker API driver: %s' % (', '.join(['"%s"' % p for p in sorted(parameters)])))
f'Unknown parameter(s) for connect_container_to_network for Docker API driver: {ups}')
ipam_config = {}
for param in ('IPv4Address', 'IPv6Address'):
if param in params:
@@ -307,7 +308,7 @@ class DockerAPIEngineDriver(EngineDriver):
output = client._get_result_tty(False, res, config['Config']['Tty'])
return output, True
else:
return "Result logged using `%s` driver" % logging_driver, False
return f"Result logged using `{logging_driver}` driver", False
def update_container(self, client, container_id, update_parameters):
result = client.post_json_to_json('/containers/{0}/update', container_id, data=update_parameters)
@@ -343,13 +344,13 @@ class DockerAPIEngineDriver(EngineDriver):
# New docker daemon versions do not allow containers to be removed
# if they are paused. Make sure we do not end up in an infinite loop.
if count == 3:
raise Exception('%s [tried to unpause three times]' % to_native(exc))
raise Exception(f'{exc} [tried to unpause three times]')
count += 1
# Unpause
try:
self.unpause_container(client, container_id)
except Exception as exc2:
raise Exception('%s [while unpausing]' % to_native(exc2))
raise Exception(f'{exc2} [while unpausing]')
# Now try again
continue
raise
@@ -369,13 +370,13 @@ class DockerAPIEngineDriver(EngineDriver):
# New docker daemon versions do not allow containers to be removed
# if they are paused. Make sure we do not end up in an infinite loop.
if count == 3:
raise Exception('%s [tried to unpause three times]' % to_native(exc))
raise Exception(f'{exc} [tried to unpause three times]')
count += 1
# Unpause
try:
self.unpause_container(client, container_id)
except Exception as exc2:
raise Exception('%s [while unpausing]' % to_native(exc2))
raise Exception(f'{exc2} [while unpausing]')
# Now try again
continue
if 'removal of container ' in exc.explanation and ' is already in progress' in exc.explanation:
@@ -389,10 +390,10 @@ class DockerAPIEngineDriver(EngineDriver):
try:
runner()
except DockerException as e:
client.fail('An unexpected Docker error occurred: {0}'.format(to_native(e)), exception=traceback.format_exc())
client.fail(f'An unexpected Docker error occurred: {e}', exception=traceback.format_exc())
except RequestException as e:
client.fail(
'An unexpected requests error occurred when trying to talk to the Docker daemon: {0}'.format(to_native(e)),
f'An unexpected requests error occurred when trying to talk to the Docker daemon: {e}',
exception=traceback.format_exc())
@@ -611,7 +612,7 @@ def _get_default_host_ip(module, client):
network = client.get_network(network_data['name'])
if network is None:
client.fail(
"Cannot inspect the network '{0}' to determine the default IP".format(network_data['name']),
f"Cannot inspect the network '{network_data['name']}' to determine the default IP",
)
if network.get('Driver') == 'bridge' and network.get('Options', {}).get('com.docker.network.bridge.host_binding_ipv4'):
ip = network['Options']['com.docker.network.bridge.host_binding_ipv4']
@@ -658,7 +659,7 @@ def _get_expected_env_value(module, client, api_version, image, value, sentry):
expected_env[parts[0]] = parts[1]
param_env = []
for key, env_value in expected_env.items():
param_env.append("%s=%s" % (key, env_value))
param_env.append(f"{key}={env_value}")
return param_env
@@ -744,7 +745,7 @@ def _preprocess_etc_hosts(module, client, api_version, value):
return value
results = []
for key, value in value.items():
results.append('%s%s%s' % (key, ':', value))
results.append(f'{key}:{value}')
return results
@@ -783,7 +784,7 @@ def _preprocess_convert_to_bytes(module, values, name, unlimited_value=None):
values[name] = value
return values
except ValueError as exc:
module.fail_json(msg='Failed to convert %s to bytes: %s' % (name, to_native(exc)))
module.fail_json(msg=f'Failed to convert {name} to bytes: {exc}')
def _get_image_labels(image):
@@ -815,7 +816,7 @@ def _preprocess_links(module, client, api_version, value):
link, alias = parsed_link
else:
link, alias = parsed_link[0], parsed_link[0]
result.append('/%s:/%s/%s' % (link, module.params['name'], alias))
result.append(f"/{link}:/{module.params['name']}/{alias}")
return result
@@ -830,11 +831,12 @@ def _ignore_mismatching_label_result(module, client, api_version, option, image,
for label in image_labels:
if label not in labels_param:
# Format label for error message
would_remove_labels.append('"%s"' % (label, ))
would_remove_labels.append(f'"{label}"')
if would_remove_labels:
labels = ', '.join(would_remove_labels)
msg = ("Some labels should be removed but are present in the base image. You can set image_label_mismatch to 'ignore' to ignore"
" this error. Labels: {0}")
client.fail(msg.format(', '.join(would_remove_labels)))
f" this error. Labels: {labels}")
client.fail(msg)
return False
@@ -860,7 +862,7 @@ def _preprocess_network_values(module, client, api_version, options, values):
for network in values['networks']:
network['id'] = _get_network_id(module, client, network['name'])
if not network['id']:
client.fail("Parameter error: network named %s could not be found. Does it exist?" % (network['name'], ))
client.fail(f"Parameter error: network named {network['name']} could not be found. Does it exist?")
if 'network_mode' in values:
values['network_mode'] = _preprocess_container_names(module, client, api_version, values['network_mode'])
@@ -878,7 +880,7 @@ def _get_network_id(module, client, network_name):
break
return network_id
except Exception as exc:
client.fail("Error getting network id for %s - %s" % (network_name, to_native(exc)))
client.fail(f"Error getting network id for {network_name} - {exc}")
def _get_values_network(module, container, api_version, options, image, host_info):
@@ -947,7 +949,7 @@ def _get_bind_from_dict(volume_dict):
if isinstance(config, dict) and config.get('bind'):
container_path = config.get('bind')
mode = config.get('mode', 'rw')
results.append("%s:%s:%s" % (host_path, container_path, mode))
results.append(f"{host_path}:{container_path}:{mode}")
return results
@@ -1133,7 +1135,7 @@ def _get_expected_values_platform(module, client, api_version, options, image, v
daemon_arch=host_info.get('Architecture') if host_info else None,
)
except ValueError as exc:
module.fail_json(msg='Error while parsing platform parameer: %s' % (to_native(exc), ))
module.fail_json(msg=f'Error while parsing platform parameer: {exc}')
return expected_values
@@ -1203,7 +1205,7 @@ def _get_expected_values_ports(module, client, api_version, options, image, valu
expected_bound_ports = {}
for container_port, config in values['published_ports'].items():
if isinstance(container_port, int):
container_port = "%s/tcp" % container_port
container_port = f"{container_port}/tcp"
if len(config) == 1:
if isinstance(config[0], int):
expected_bound_ports[container_port] = [{'HostIp': "0.0.0.0", 'HostPort': config[0]}]
@@ -1243,7 +1245,7 @@ def _set_values_ports(module, data, api_version, options, values):
if len(port_definition) == 2:
proto = port_definition[1]
port = port_definition[0]
exposed_ports['%s/%s' % (port, proto)] = {}
exposed_ports[f'{port}/{proto}'] = {}
data['ExposedPorts'] = exposed_ports
if 'published_ports' in values:
if 'HostConfig' not in data:
@@ -1282,9 +1284,9 @@ def _preprocess_container_names(module, client, api_version, value):
if container is None:
# If we cannot find the container, issue a warning and continue with
# what the user specified.
module.warn('Cannot find a container with name or ID "{0}"'.format(container_name))
module.warn(f'Cannot find a container with name or ID "{container_name}"')
return value
return 'container:{0}'.format(container['Id'])
return f"container:{container['Id']}"
def _get_value_command(module, container, api_version, options, image, host_info):
+39 -43
View File
@@ -8,7 +8,7 @@ from __future__ import annotations
import re
from time import sleep
from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.module_utils.common.text.converters import to_text
from ansible_collections.community.docker.plugins.module_utils.util import (
DifferenceTracker,
@@ -102,11 +102,11 @@ class ContainerManager(DockerBaseClass):
if re.match(r'^\[[0-9a-fA-F:]+\]$', self.param_default_host_ip):
valid_ip = True
if re.match(r'^[0-9a-fA-F:]+$', self.param_default_host_ip):
self.param_default_host_ip = '[{0}]'.format(self.param_default_host_ip)
self.param_default_host_ip = f'[{self.param_default_host_ip}]'
valid_ip = True
if not valid_ip:
self.fail('The value of default_host_ip must be an empty string, an IPv4 address, '
'or an IPv6 address. Got "{0}" instead.'.format(self.param_default_host_ip))
f'or an IPv6 address. Got "{self.param_default_host_ip}" instead.')
def _collect_all_options(self, active_options):
all_options = {}
@@ -157,23 +157,23 @@ class ContainerManager(DockerBaseClass):
key_main = comp_aliases.get(key)
if key_main is None:
if key_main in all_module_options:
self.fail("The module option '%s' cannot be specified in the comparisons dict, "
"since it does not correspond to container's state!" % key)
self.fail(f"The module option '{key}' cannot be specified in the comparisons dict, "
"since it does not correspond to container's state!")
if key not in self.all_options or self.all_options[key].not_an_ansible_option:
self.fail("Unknown module option '%s' in comparisons dict!" % key)
self.fail(f"Unknown module option '{key}' in comparisons dict!")
key_main = key
if key_main in comp_aliases_used:
self.fail("Both '%s' and '%s' (aliases of %s) are specified in comparisons dict!" % (key, comp_aliases_used[key_main], key_main))
self.fail(f"Both '{key}' and '{comp_aliases_used[key_main]}' (aliases of {key_main}) are specified in comparisons dict!")
comp_aliases_used[key_main] = key
# Check value and update accordingly
if value in ('strict', 'ignore'):
self.all_options[key_main].comparison = value
elif value == 'allow_more_present':
if self.all_options[key_main].comparison_type == 'value':
self.fail("Option '%s' is a value and not a set/list/dict, so its comparison cannot be %s" % (key, value))
self.fail(f"Option '{key}' is a value and not a set/list/dict, so its comparison cannot be {value}")
self.all_options[key_main].comparison = value
else:
self.fail("Unknown comparison mode '%s'!" % value)
self.fail(f"Unknown comparison mode '{value}'!")
# Copy values
for option in self.all_options.values():
if option.copy_comparison_from is not None:
@@ -228,8 +228,8 @@ class ContainerManager(DockerBaseClass):
if result is None:
if accept_removal:
return result
msg = 'Encontered vanished container while waiting for container "{0}"'
self.fail(msg.format(container_id))
msg = f'Encontered vanished container while waiting for container "{container_id}"'
self.fail(msg)
# Check container state
state_info = result.get('State') or {}
if health_state:
@@ -238,13 +238,13 @@ class ContainerManager(DockerBaseClass):
if complete_states is not None and state in complete_states:
return result
if wait_states is not None and state not in wait_states:
msg = 'Encontered unexpected state "{1}" while waiting for container "{0}"'
self.fail(msg.format(container_id, state), container=result)
msg = f'Encontered unexpected state "{state}" while waiting for container "{container_id}"'
self.fail(msg, container=result)
# Wait
if max_wait is not None:
if total_wait > max_wait or delay < 1E-4:
msg = 'Timeout of {1} seconds exceeded while waiting for container "{0}"'
self.fail(msg.format(container_id, max_wait), container=result)
msg = f'Timeout of {max_wait} seconds exceeded while waiting for container "{container_id}"'
self.fail(msg, container=result)
if total_wait + delay > max_wait:
delay = max_wait - total_wait
sleep(delay)
@@ -373,9 +373,7 @@ class ContainerManager(DockerBaseClass):
else:
self.engine_driver.unpause_container(self.client, container.id)
except Exception as exc:
self.fail("Error %s container %s: %s" % (
"pausing" if self.param_paused else "unpausing", container.id, to_native(exc)
))
self.fail(f"Error {'pausing' if self.param_paused else 'unpausing'} container {container.id}: {exc}")
container = self._get_container(container.id)
self.results['changed'] = True
self.results['actions'].append(dict(set_paused=self.param_paused))
@@ -440,14 +438,14 @@ class ContainerManager(DockerBaseClass):
if is_image_name_id(image_parameter):
image = self.engine_driver.inspect_image_by_id(self.client, image_parameter)
if image is None:
self.client.fail("Cannot find image with ID %s" % (image_parameter, ))
self.client.fail(f"Cannot find image with ID {image_parameter}")
else:
repository, tag = parse_repository_tag(image_parameter)
if not tag:
tag = "latest"
image = self.engine_driver.inspect_image_by_name(self.client, repository, tag)
if not image and self.param_pull == "never":
self.client.fail("Cannot find image with name %s:%s, and pull=never" % (repository, tag))
self.client.fail(f"Cannot find image with name {repository}:{tag}, and pull=never")
if not image or self.param_pull == "always":
if not self.check_mode:
self.log("Pull the image.")
@@ -455,16 +453,16 @@ class ContainerManager(DockerBaseClass):
self.client, repository, tag, platform=self.module.params['platform'])
if alreadyToLatest:
self.results['changed'] = False
self.results['actions'].append(dict(pulled_image="%s:%s" % (repository, tag), changed=False))
self.results['actions'].append(dict(pulled_image=f"{repository}:{tag}", changed=False))
else:
self.results['changed'] = True
self.results['actions'].append(dict(pulled_image="%s:%s" % (repository, tag), changed=True))
self.results['actions'].append(dict(pulled_image=f"{repository}:{tag}", changed=True))
elif not image or self.param_pull_check_mode_behavior == 'always':
# If the image is not there, or pull_check_mode_behavior == 'always', claim we'll
# pull. (Implicitly: if the image is there, claim it already was latest unless
# pull_check_mode_behavior == 'always'.)
self.results['changed'] = True
action = dict(pulled_image="%s:%s" % (repository, tag))
action = dict(pulled_image=f"{repository}:{tag}")
if not image:
action['changed'] = True
self.results['actions'].append(action)
@@ -620,7 +618,7 @@ class ContainerManager(DockerBaseClass):
if network.get('links'):
expected_links = []
for link, alias in network['links']:
expected_links.append("%s:%s" % (link, alias))
expected_links.append(f"{link}:{alias}")
if not compare_generic(expected_links, network_info.get('Links'), 'allow_more_present', 'set'):
diff = True
if network.get('mac_address') and network['mac_address'] != network_info.get('MacAddress'):
@@ -674,7 +672,7 @@ class ContainerManager(DockerBaseClass):
self.diff['differences'] = [dict(network_differences=network_differences)]
for netdiff in network_differences:
self.diff_tracker.add(
'network.{0}'.format(netdiff['parameter']['name']),
f"network.{netdiff['parameter']['name']}",
parameter=netdiff['parameter'],
active=netdiff['container']
)
@@ -691,7 +689,7 @@ class ContainerManager(DockerBaseClass):
self.diff['differences'] = [dict(purge_networks=extra_networks)]
for extra_network in extra_networks:
self.diff_tracker.add(
'network.{0}'.format(extra_network['name']),
f"network.{extra_network['name']}",
active=extra_network
)
self.results['changed'] = True
@@ -707,18 +705,17 @@ class ContainerManager(DockerBaseClass):
try:
self.engine_driver.disconnect_container_from_network(self.client, container.id, diff['parameter']['id'])
except Exception as exc:
self.fail("Error disconnecting container from network %s - %s" % (diff['parameter']['name'],
to_native(exc)))
self.fail(f"Error disconnecting container from network {diff['parameter']['name']} - {exc}")
# connect to the network
self.results['actions'].append(dict(added_to_network=diff['parameter']['name'], network_parameters=diff['parameter']))
if not self.check_mode:
params = {key: value for key, value in diff['parameter'].items() if key not in ('id', 'name')}
try:
self.log("Connecting container to network %s" % diff['parameter']['id'])
self.log(f"Connecting container to network {diff['parameter']['id']}")
self.log(params, pretty_print=True)
self.engine_driver.connect_container_to_network(self.client, container.id, diff['parameter']['id'], params)
except Exception as exc:
self.fail("Error connecting container to network %s - %s" % (diff['parameter']['name'], to_native(exc)))
self.fail(f"Error connecting container to network {diff['parameter']['name']} - {exc}")
return self._get_container(container.id)
def _purge_networks(self, container, networks):
@@ -728,14 +725,13 @@ class ContainerManager(DockerBaseClass):
try:
self.engine_driver.disconnect_container_from_network(self.client, container.id, network['name'])
except Exception as exc:
self.fail("Error disconnecting container from network %s - %s" % (network['name'],
to_native(exc)))
self.fail(f"Error disconnecting container from network {network['name']} - {exc}")
return self._get_container(container.id)
def container_create(self, image):
create_parameters = self._compose_create_parameters(image)
self.log("create container")
self.log("image: %s parameters:" % image)
self.log(f"image: {image} parameters:")
self.log(create_parameters, pretty_print=True)
networks = {}
if self.param_networks_cli_compatible and self.module.params['networks']:
@@ -754,19 +750,19 @@ class ContainerManager(DockerBaseClass):
try:
container_id = self.engine_driver.create_container(self.client, self.param_name, create_parameters, networks=networks)
except Exception as exc:
self.fail("Error creating container: %s" % to_native(exc))
self.fail(f"Error creating container: {exc}")
return self._get_container(container_id)
return new_container
def container_start(self, container_id):
self.log("start container %s" % (container_id))
self.log(f"start container {container_id}")
self.results['actions'].append(dict(started=container_id))
self.results['changed'] = True
if not self.check_mode:
try:
self.engine_driver.start_container(self.client, container_id)
except Exception as exc:
self.fail("Error starting container %s: %s" % (container_id, to_native(exc)))
self.fail(f"Error starting container {container_id}: {exc}")
if self.module.params['detach'] is False:
status = self.engine_driver.wait_for_container(self.client, container_id)
@@ -798,18 +794,18 @@ class ContainerManager(DockerBaseClass):
def container_remove(self, container_id, link=False, force=False):
volume_state = (not self.param_keep_volumes)
self.log("remove container container:%s v:%s link:%s force%s" % (container_id, volume_state, link, force))
self.log(f"remove container container:{container_id} v:{volume_state} link:{link} force{force}")
self.results['actions'].append(dict(removed=container_id, volume_state=volume_state, link=link, force=force))
self.results['changed'] = True
if not self.check_mode:
try:
self.engine_driver.remove_container(self.client, container_id, remove_volumes=volume_state, link=link, force=force)
except Exception as exc:
self.client.fail("Error removing container %s: %s" % (container_id, to_native(exc)))
self.client.fail(f"Error removing container {container_id}: {exc}")
def container_update(self, container_id, update_parameters):
if update_parameters:
self.log("update container %s" % (container_id))
self.log(f"update container {container_id}")
self.log(update_parameters, pretty_print=True)
self.results['actions'].append(dict(updated=container_id, update_parameters=update_parameters))
self.results['changed'] = True
@@ -817,7 +813,7 @@ class ContainerManager(DockerBaseClass):
try:
self.engine_driver.update_container(self.client, container_id, update_parameters)
except Exception as exc:
self.fail("Error updating container %s: %s" % (container_id, to_native(exc)))
self.fail(f"Error updating container {container_id}: {exc}")
return self._get_container(container_id)
def container_kill(self, container_id):
@@ -827,7 +823,7 @@ class ContainerManager(DockerBaseClass):
try:
self.engine_driver.kill_container(self.client, container_id, kill_signal=self.param_kill_signal)
except Exception as exc:
self.fail("Error killing container %s: %s" % (container_id, to_native(exc)))
self.fail(f"Error killing container {container_id}: {exc}")
def container_restart(self, container_id):
self.results['actions'].append(dict(restarted=container_id, timeout=self.module.params['stop_timeout']))
@@ -836,7 +832,7 @@ class ContainerManager(DockerBaseClass):
try:
self.engine_driver.restart_container(self.client, container_id, self.module.params['stop_timeout'] or 10)
except Exception as exc:
self.fail("Error restarting container %s: %s" % (container_id, to_native(exc)))
self.fail(f"Error restarting container {container_id}: {exc}")
return self._get_container(container_id)
def container_stop(self, container_id):
@@ -849,7 +845,7 @@ class ContainerManager(DockerBaseClass):
try:
self.engine_driver.stop_container(self.client, container_id, self.module.params['stop_timeout'])
except Exception as exc:
self.fail("Error stopping container %s: %s" % (container_id, to_native(exc)))
self.fail(f"Error stopping container {container_id}: {exc}")
def run_module(engine_driver):
+7 -8
View File
@@ -89,7 +89,7 @@ class DockerSocketHandlerBase(object):
if data is None:
# no data available
return
self._log('read {0} bytes'.format(len(data)))
self._log(f'read {len(data)} bytes')
if len(data) == 0:
# Stream EOF
self._eof = True
@@ -123,7 +123,7 @@ class DockerSocketHandlerBase(object):
if len(self._write_buffer) > 0:
written = write_to_socket(self._sock, self._write_buffer)
self._write_buffer = self._write_buffer[written:]
self._log('wrote {0} bytes, {1} are left'.format(written, len(self._write_buffer)))
self._log(f'wrote {written} bytes, {len(self._write_buffer)} are left')
if len(self._write_buffer) > 0:
self._selector.modify(self._sock, self._selectors.EVENT_READ | self._selectors.EVENT_WRITE)
else:
@@ -147,14 +147,13 @@ class DockerSocketHandlerBase(object):
return True
if timeout is not None:
timeout -= PARAMIKO_POLL_TIMEOUT
self._log('select... ({0})'.format(timeout))
self._log(f'select... ({timeout})')
events = self._selector.select(timeout)
for key, event in events:
if key.fileobj == self._sock:
self._log(
'select event read:{0} write:{1}'.format(
event & self._selectors.EVENT_READ != 0,
event & self._selectors.EVENT_WRITE != 0))
ev_read = event & self._selectors.EVENT_READ != 0
ev_write = event & self._selectors.EVENT_WRITE != 0
self._log(f'select event read:{ev_read} write:{ev_write}')
if event & self._selectors.EVENT_READ != 0:
self._read()
if event & self._selectors.EVENT_WRITE != 0:
@@ -183,7 +182,7 @@ class DockerSocketHandlerBase(object):
elif stream_id == docker_socket.STDERR:
stderr.append(data)
else:
raise ValueError('{0} is not a valid stream ID'.format(stream_id))
raise ValueError(f'{stream_id} is not a valid stream ID')
self.end_of_writing()
+1 -1
View File
@@ -44,7 +44,7 @@ def shutdown_writing(sock, log=_empty_writer):
sock.shutdown(pysocket.SHUT_WR)
except TypeError as e:
# probably: "TypeError: shutdown() takes 1 positional argument but 2 were given"
log('Shutting down for writing not possible; trying shutdown instead: {0}'.format(e))
log(f'Shutting down for writing not possible; trying shutdown instead: {e}')
sock.shutdown()
elif isinstance(sock, getattr(pysocket, 'SocketIO')):
sock._sock.shutdown(pysocket.SHUT_WR)
+8 -10
View File
@@ -15,8 +15,6 @@ except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.docker.plugins.module_utils.version import LooseVersion
from ansible_collections.community.docker.plugins.module_utils.common import AnsibleDockerClient
@@ -38,7 +36,7 @@ class AnsibleDockerSwarmClient(AnsibleDockerClient):
try:
info = self.info()
except APIError as exc:
self.fail("Failed to get node information for %s" % to_native(exc))
self.fail(f"Failed to get node information for {exc}")
if info:
json_str = json.dumps(info, ensure_ascii=False)
@@ -166,9 +164,9 @@ class AnsibleDockerSwarmClient(AnsibleDockerClient):
if exc.status_code == 404:
if skip_missing:
return None
self.fail("Error while reading from Swarm manager: %s" % to_native(exc))
self.fail(f"Error while reading from Swarm manager: {exc}")
except Exception as exc:
self.fail("Error inspecting swarm node: %s" % exc)
self.fail(f"Error inspecting swarm node: {exc}")
json_str = json.dumps(node_info, ensure_ascii=False)
node_info = json.loads(json_str)
@@ -197,9 +195,9 @@ class AnsibleDockerSwarmClient(AnsibleDockerClient):
except APIError as exc:
if exc.status_code == 503:
self.fail("Cannot inspect node: To inspect node execute module on Swarm Manager")
self.fail("Error while reading from Swarm manager: %s" % to_native(exc))
self.fail(f"Error while reading from Swarm manager: {exc}")
except Exception as exc:
self.fail("Error inspecting swarm node: %s" % exc)
self.fail(f"Error inspecting swarm node: {exc}")
json_str = json.dumps(node_info, ensure_ascii=False)
node_info = json.loads(json_str)
@@ -265,15 +263,15 @@ class AnsibleDockerSwarmClient(AnsibleDockerClient):
service_info = self.inspect_service(service_id)
except NotFound as exc:
if skip_missing is False:
self.fail("Error while reading from Swarm manager: %s" % to_native(exc))
self.fail(f"Error while reading from Swarm manager: {exc}")
else:
return None
except APIError as exc:
if exc.status_code == 503:
self.fail("Cannot inspect service: To inspect service execute module on Swarm Manager")
self.fail("Error inspecting swarm service: %s" % exc)
self.fail(f"Error inspecting swarm service: {exc}")
except Exception as exc:
self.fail("Error inspecting swarm service: %s" % exc)
self.fail(f"Error inspecting swarm service: {exc}")
json_str = json.dumps(service_info, ensure_ascii=False)
service_info = json.loads(json_str)
+8 -13
View File
@@ -36,7 +36,7 @@ DOCKER_COMMON_ARGS = dict(
)
DOCKER_COMMON_ARGS_VARS = dict([
[option_name, 'ansible_docker_%s' % option_name]
[option_name, f'ansible_docker_{option_name}']
for option_name in DOCKER_COMMON_ARGS
if option_name != 'debug'
])
@@ -93,9 +93,9 @@ def log_debug(msg, pretty_print=False):
with open('docker.log', 'a') as log_file:
if pretty_print:
log_file.write(json.dumps(msg, sort_keys=True, indent=4, separators=(',', ': ')))
log_file.write(u'\n')
log_file.write('\n')
else:
log_file.write(msg + u'\n')
log_file.write(f"{msg}\n")
class DockerBaseClass(object):
@@ -289,13 +289,9 @@ def sanitize_labels(labels, labels_field, client=None, module=None):
return
for k, v in list(labels.items()):
if not isinstance(k, str):
fail(
"The key {key!r} of {field} is not a string!".format(
field=labels_field, key=k))
fail(f"The key {k!r} of {labels_field} is not a string!")
if isinstance(v, (bool, float)):
fail(
"The value {value!r} for {key!r} of {field} is not a string or something than can be safely converted to a string!".format(
field=labels_field, key=k, value=v))
fail(f"The value {v!r} for {k!r} of {labels_field} is not a string or something than can be safely converted to a string!")
labels[k] = to_text(v)
@@ -328,7 +324,7 @@ def convert_duration_to_nanosecond(time_str):
Return time duration in nanosecond.
"""
if not isinstance(time_str, str):
raise ValueError('Missing unit in duration - %s' % time_str)
raise ValueError(f'Missing unit in duration - {time_str}')
regex = re.compile(
r'^(((?P<hours>\d+)h)?'
@@ -340,7 +336,7 @@ def convert_duration_to_nanosecond(time_str):
parts = regex.match(time_str)
if not parts:
raise ValueError('Invalid time duration - %s' % time_str)
raise ValueError(f'Invalid time duration - {time_str}')
parts = parts.groupdict()
time_params = {}
@@ -389,8 +385,7 @@ def normalize_healthcheck(healthcheck, normalize_test=False):
value = int(value)
except ValueError:
raise ValueError(
'Cannot parse number of retries for healthcheck. '
'Expected an integer, got "{0}".'.format(value)
f'Cannot parse number of retries for healthcheck. Expected an integer, got "{value}".'
)
if key == 'test' and value and normalize_test:
value = normalize_healthcheck_test(value)