Reformat code with black and isort.

This commit is contained in:
Felix Fontein
2025-10-06 18:34:59 +02:00
parent f45232635c
commit d65d37e9e9
132 changed files with 17581 additions and 14729 deletions
+186 -152
View File
@@ -285,23 +285,21 @@ import traceback
from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.common.text.formatters import human_to_bytes
from ansible_collections.community.docker.plugins.module_utils._api.utils.utils import (
parse_repository_tag,
)
from ansible_collections.community.docker.plugins.module_utils.common_cli import (
AnsibleModuleDockerClient,
DockerException,
)
from ansible_collections.community.docker.plugins.module_utils.util import (
DockerBaseClass,
clean_dict_booleans_for_docker_api,
is_image_name_id,
is_valid_tag,
)
from ansible_collections.community.docker.plugins.module_utils.version import LooseVersion
from ansible_collections.community.docker.plugins.module_utils._api.utils.utils import (
parse_repository_tag,
from ansible_collections.community.docker.plugins.module_utils.version import (
LooseVersion,
)
@@ -309,15 +307,15 @@ def convert_to_bytes(value, module, name, unlimited_value=None):
if value is None:
return value
try:
if unlimited_value is not None and value in ('unlimited', str(unlimited_value)):
if unlimited_value is not None and value in ("unlimited", str(unlimited_value)):
return unlimited_value
return human_to_bytes(value)
except ValueError as exc:
module.fail_json(msg=f'Failed to convert {name} to bytes: {exc}')
module.fail_json(msg=f"Failed to convert {name} to bytes: {exc}")
def dict_to_list(dictionary, concat='='):
return [f'{k}{concat}{v}' for k, v in sorted(dictionary.items())]
def dict_to_list(dictionary, concat="="):
return [f"{k}{concat}{v}" for k, v in sorted(dictionary.items())]
def _quote_csv(input):
@@ -334,47 +332,59 @@ class ImageBuilder(DockerBaseClass):
self.check_mode = self.client.check_mode
parameters = self.client.module.params
self.cache_from = parameters['cache_from']
self.pull = parameters['pull']
self.network = parameters['network']
self.nocache = parameters['nocache']
self.etc_hosts = clean_dict_booleans_for_docker_api(parameters['etc_hosts'])
self.args = clean_dict_booleans_for_docker_api(parameters['args'])
self.target = parameters['target']
self.platform = parameters['platform']
self.shm_size = convert_to_bytes(parameters['shm_size'], self.client.module, 'shm_size')
self.labels = clean_dict_booleans_for_docker_api(parameters['labels'])
self.rebuild = parameters['rebuild']
self.secrets = parameters['secrets']
self.outputs = parameters['outputs']
self.cache_from = parameters["cache_from"]
self.pull = parameters["pull"]
self.network = parameters["network"]
self.nocache = parameters["nocache"]
self.etc_hosts = clean_dict_booleans_for_docker_api(parameters["etc_hosts"])
self.args = clean_dict_booleans_for_docker_api(parameters["args"])
self.target = parameters["target"]
self.platform = parameters["platform"]
self.shm_size = convert_to_bytes(
parameters["shm_size"], self.client.module, "shm_size"
)
self.labels = clean_dict_booleans_for_docker_api(parameters["labels"])
self.rebuild = parameters["rebuild"]
self.secrets = parameters["secrets"]
self.outputs = parameters["outputs"]
buildx = self.client.get_client_plugin_info('buildx')
buildx = self.client.get_client_plugin_info("buildx")
if buildx is None:
self.fail(f'Docker CLI {self.client.get_cli()} does not have the buildx plugin installed')
buildx_version = buildx['Version'].lstrip('v')
self.fail(
f"Docker CLI {self.client.get_cli()} does not have the buildx plugin installed"
)
buildx_version = buildx["Version"].lstrip("v")
if self.secrets:
for secret in self.secrets:
if secret['type'] in ('env', 'value'):
if LooseVersion(buildx_version) < LooseVersion('0.6.0'):
self.fail(f'The Docker buildx plugin has version {buildx_version}, but 0.6.0 is needed for secrets of type=env and type=value')
if secret["type"] in ("env", "value"):
if LooseVersion(buildx_version) < LooseVersion("0.6.0"):
self.fail(
f"The Docker buildx plugin has version {buildx_version}, but 0.6.0 is needed for secrets of type=env and type=value"
)
if self.outputs and len(self.outputs) > 1:
if LooseVersion(buildx_version) < LooseVersion('0.13.0'):
self.fail(f'The Docker buildx plugin has version {buildx_version}, but 0.13.0 is needed to specify more than one output')
if LooseVersion(buildx_version) < LooseVersion("0.13.0"):
self.fail(
f"The Docker buildx plugin has version {buildx_version}, but 0.13.0 is needed to specify more than one output"
)
self.path = parameters['path']
self.path = parameters["path"]
if not os.path.isdir(self.path):
self.fail(f'"{self.path}" is not an existing directory')
self.dockerfile = parameters['dockerfile']
if self.dockerfile and not os.path.isfile(os.path.join(self.path, self.dockerfile)):
self.fail(f'"{os.path.join(self.path, self.dockerfile)}" is not an existing file')
self.dockerfile = parameters["dockerfile"]
if self.dockerfile and not os.path.isfile(
os.path.join(self.path, self.dockerfile)
):
self.fail(
f'"{os.path.join(self.path, self.dockerfile)}" is not an existing file'
)
self.name = parameters['name']
self.tag = parameters['tag']
self.name = parameters["name"]
self.tag = parameters["tag"]
if not is_valid_tag(self.tag, allow_empty=True):
self.fail(f'"{self.tag}" is not a valid docker tag')
if is_image_name_id(self.name):
self.fail('Image name must not be a digest')
self.fail("Image name must not be a digest")
# If name contains a tag, it takes precedence over tag parameter.
repo, repo_tag = parse_repository_tag(self.name)
@@ -383,25 +393,27 @@ class ImageBuilder(DockerBaseClass):
self.tag = repo_tag
if is_image_name_id(self.tag):
self.fail('Image name must not contain a digest, but have a tag')
self.fail("Image name must not contain a digest, but have a tag")
if self.outputs:
found = False
name_tag = f'{self.name}:{self.tag}'
name_tag = f"{self.name}:{self.tag}"
for output in self.outputs:
if output['type'] == 'image':
if not output['name']:
if output["type"] == "image":
if not output["name"]:
# Since we no longer pass --tag if --output is provided, we need to set this manually
output['name'] = [name_tag]
if output['name'] and name_tag in output['name']:
output["name"] = [name_tag]
if output["name"] and name_tag in output["name"]:
found = True
if not found:
self.outputs.append({
'type': 'image',
'name': [name_tag],
'push': False,
})
if LooseVersion(buildx_version) < LooseVersion('0.13.0'):
self.outputs.append(
{
"type": "image",
"name": [name_tag],
"push": False,
}
)
if LooseVersion(buildx_version) < LooseVersion("0.13.0"):
self.fail(
f"The output does not include an image with name {name_tag}, and the Docker"
f" buildx plugin has version {buildx_version} which only supports one output."
@@ -417,78 +429,86 @@ class ImageBuilder(DockerBaseClass):
def add_args(self, args):
environ_update = {}
if not self.outputs:
args.extend(['--tag', f'{self.name}:{self.tag}'])
args.extend(["--tag", f"{self.name}:{self.tag}"])
if self.dockerfile:
args.extend(['--file', os.path.join(self.path, self.dockerfile)])
args.extend(["--file", os.path.join(self.path, self.dockerfile)])
if self.cache_from:
self.add_list_arg(args, '--cache-from', self.cache_from)
self.add_list_arg(args, "--cache-from", self.cache_from)
if self.pull:
args.append('--pull')
args.append("--pull")
if self.network:
args.extend(['--network', self.network])
args.extend(["--network", self.network])
if self.nocache:
args.append('--no-cache')
args.append("--no-cache")
if self.etc_hosts:
self.add_list_arg(args, '--add-host', dict_to_list(self.etc_hosts, ':'))
self.add_list_arg(args, "--add-host", dict_to_list(self.etc_hosts, ":"))
if self.args:
self.add_list_arg(args, '--build-arg', dict_to_list(self.args))
self.add_list_arg(args, "--build-arg", dict_to_list(self.args))
if self.target:
args.extend(['--target', self.target])
args.extend(["--target", self.target])
if self.platform:
for platform in self.platform:
args.extend(['--platform', platform])
args.extend(["--platform", platform])
if self.shm_size:
args.extend(['--shm-size', str(self.shm_size)])
args.extend(["--shm-size", str(self.shm_size)])
if self.labels:
self.add_list_arg(args, '--label', dict_to_list(self.labels))
self.add_list_arg(args, "--label", dict_to_list(self.labels))
if self.secrets:
random_prefix = None
for index, secret in enumerate(self.secrets):
sid = secret['id']
if secret['type'] == 'file':
src = secret['src']
args.extend(['--secret', f'id={sid},type=file,src={src}'])
if secret['type'] == 'env':
env = secret['src']
args.extend(['--secret', f'id={sid},type=env,env={env}'])
if secret['type'] == 'value':
sid = secret["id"]
if secret["type"] == "file":
src = secret["src"]
args.extend(["--secret", f"id={sid},type=file,src={src}"])
if secret["type"] == "env":
env = secret["src"]
args.extend(["--secret", f"id={sid},type=env,env={env}"])
if secret["type"] == "value":
# We pass values on using environment variables. The user has been warned in the documentation
# that they should only use this mechanism when being comfortable with it.
if random_prefix is None:
# Use /dev/urandom to generate some entropy to make the environment variable's name unguessable
random_prefix = base64.b64encode(os.urandom(16)).decode('utf-8').replace('=', '')
env_name = f'ANSIBLE_DOCKER_COMPOSE_ENV_SECRET_{random_prefix}_{index}'
environ_update[env_name] = secret['value']
args.extend(['--secret', f'id={sid},type=env,env={env_name}'])
random_prefix = (
base64.b64encode(os.urandom(16))
.decode("utf-8")
.replace("=", "")
)
env_name = (
f"ANSIBLE_DOCKER_COMPOSE_ENV_SECRET_{random_prefix}_{index}"
)
environ_update[env_name] = secret["value"]
args.extend(["--secret", f"id={sid},type=env,env={env_name}"])
if self.outputs:
for output in self.outputs:
subargs = []
if output['type'] == 'local':
dest = output['dest']
subargs.extend(['type=local', f'dest={dest}'])
if output['type'] == 'tar':
dest = output['dest']
subargs.extend(['type=tar', f'dest={dest}'])
if output['type'] == 'oci':
dest = output['dest']
subargs.extend(['type=oci', f'dest={dest}'])
if output['type'] == 'docker':
subargs.append('type=docker')
dest = output['dest']
if output['dest'] is not None:
subargs.append(f'dest={dest}')
if output['context'] is not None:
context = output['context']
subargs.append(f'context={context}')
if output['type'] == 'image':
subargs.append('type=image')
if output['name'] is not None:
name = ','.join(output['name'])
subargs.append(f'name={name}')
if output['push']:
subargs.append('push=true')
if output["type"] == "local":
dest = output["dest"]
subargs.extend(["type=local", f"dest={dest}"])
if output["type"] == "tar":
dest = output["dest"]
subargs.extend(["type=tar", f"dest={dest}"])
if output["type"] == "oci":
dest = output["dest"]
subargs.extend(["type=oci", f"dest={dest}"])
if output["type"] == "docker":
subargs.append("type=docker")
dest = output["dest"]
if output["dest"] is not None:
subargs.append(f"dest={dest}")
if output["context"] is not None:
context = output["context"]
subargs.append(f"context={context}")
if output["type"] == "image":
subargs.append("type=image")
if output["name"] is not None:
name = ",".join(output["name"])
subargs.append(f"name={name}")
if output["push"]:
subargs.append("push=true")
if subargs:
args.extend(['--output', ','.join(_quote_csv(subarg) for subarg in subargs)])
args.extend(
["--output", ",".join(_quote_csv(subarg) for subarg in subargs)]
)
return environ_update
def build_image(self):
@@ -500,82 +520,93 @@ class ImageBuilder(DockerBaseClass):
)
if image:
if self.rebuild == 'never':
if self.rebuild == "never":
return results
results['changed'] = True
results["changed"] = True
if not self.check_mode:
args = ['buildx', 'build', '--progress', 'plain']
args = ["buildx", "build", "--progress", "plain"]
environ_update = self.add_args(args)
args.extend(['--', self.path])
rc, stdout, stderr = self.client.call_cli(*args, environ_update=environ_update)
args.extend(["--", self.path])
rc, stdout, stderr = self.client.call_cli(
*args, environ_update=environ_update
)
if rc != 0:
self.fail(f'Building {self.name}:{self.tag} failed', stdout=to_native(stdout), stderr=to_native(stderr), command=args)
results['stdout'] = to_native(stdout)
results['stderr'] = to_native(stderr)
results['image'] = self.client.find_image(self.name, self.tag) or {}
results['command'] = args
self.fail(
f"Building {self.name}:{self.tag} failed",
stdout=to_native(stdout),
stderr=to_native(stderr),
command=args,
)
results["stdout"] = to_native(stdout)
results["stderr"] = to_native(stderr)
results["image"] = self.client.find_image(self.name, self.tag) or {}
results["command"] = args
return results
def main():
argument_spec = dict(
name=dict(type='str', required=True),
tag=dict(type='str', default='latest'),
path=dict(type='path', required=True),
dockerfile=dict(type='str'),
cache_from=dict(type='list', elements='str'),
pull=dict(type='bool', default=False),
network=dict(type='str'),
nocache=dict(type='bool', default=False),
etc_hosts=dict(type='dict'),
args=dict(type='dict'),
target=dict(type='str'),
platform=dict(type='list', elements='str'),
shm_size=dict(type='str'),
labels=dict(type='dict'),
rebuild=dict(type='str', choices=['never', 'always'], default='never'),
name=dict(type="str", required=True),
tag=dict(type="str", default="latest"),
path=dict(type="path", required=True),
dockerfile=dict(type="str"),
cache_from=dict(type="list", elements="str"),
pull=dict(type="bool", default=False),
network=dict(type="str"),
nocache=dict(type="bool", default=False),
etc_hosts=dict(type="dict"),
args=dict(type="dict"),
target=dict(type="str"),
platform=dict(type="list", elements="str"),
shm_size=dict(type="str"),
labels=dict(type="dict"),
rebuild=dict(type="str", choices=["never", "always"], default="never"),
secrets=dict(
type='list',
elements='dict',
type="list",
elements="dict",
options=dict(
id=dict(type='str', required=True),
type=dict(type='str', choices=['file', 'env', 'value'], required=True),
src=dict(type='path'),
env=dict(type='str'),
value=dict(type='str', no_log=True),
id=dict(type="str", required=True),
type=dict(type="str", choices=["file", "env", "value"], required=True),
src=dict(type="path"),
env=dict(type="str"),
value=dict(type="str", no_log=True),
),
required_if=[
('type', 'file', ['src']),
('type', 'env', ['env']),
('type', 'value', ['value']),
("type", "file", ["src"]),
("type", "env", ["env"]),
("type", "value", ["value"]),
],
mutually_exclusive=[
('src', 'env', 'value'),
("src", "env", "value"),
],
no_log=False,
),
outputs=dict(
type='list',
elements='dict',
type="list",
elements="dict",
options=dict(
type=dict(type='str', choices=['local', 'tar', 'oci', 'docker', 'image'], required=True),
dest=dict(type='path'),
context=dict(type='str'),
name=dict(type='list', elements='str'),
push=dict(type='bool', default=False),
type=dict(
type="str",
choices=["local", "tar", "oci", "docker", "image"],
required=True,
),
dest=dict(type="path"),
context=dict(type="str"),
name=dict(type="list", elements="str"),
push=dict(type="bool", default=False),
),
required_if=[
('type', 'local', ['dest']),
('type', 'tar', ['dest']),
('type', 'oci', ['dest']),
("type", "local", ["dest"]),
("type", "tar", ["dest"]),
("type", "oci", ["dest"]),
],
mutually_exclusive=[
('dest', 'name'),
('dest', 'push'),
('context', 'name'),
('context', 'push'),
("dest", "name"),
("dest", "push"),
("context", "name"),
("context", "push"),
],
),
)
@@ -590,8 +621,11 @@ def main():
results = ImageBuilder(client).build_image()
client.module.exit_json(**results)
except DockerException as e:
client.fail(f'An unexpected Docker error occurred: {e}', exception=traceback.format_exc())
client.fail(
f"An unexpected Docker error occurred: {e}",
exception=traceback.format_exc(),
)
if __name__ == '__main__':
if __name__ == "__main__":
main()