mirror of
https://github.com/ansible-collections/community.docker.git
synced 2026-07-29 03:46:55 +00:00
Add typing information, 1/2 (#1176)
* Re-enable typing and improve config. * Make mypy pass. * Improve settings. * First batch of types. * Add more type hints. * Fixes. * Format. * Fix split_port() without returning to previous type chaos. * Continue with type hints (and ignores).
This commit is contained in:
@@ -80,7 +80,7 @@ import re
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
module = AnsibleModule({}, supports_check_mode=True)
|
||||
|
||||
cpuset_path = "/proc/self/cpuset"
|
||||
|
||||
@@ -437,6 +437,7 @@ actions:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.validation import check_type_int
|
||||
|
||||
@@ -455,26 +456,32 @@ from ansible_collections.community.docker.plugins.module_utils._version import (
|
||||
|
||||
|
||||
class ServicesManager(BaseComposeManager):
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleModuleDockerClient) -> None:
|
||||
super().__init__(client)
|
||||
parameters = self.client.module.params
|
||||
|
||||
self.state = parameters["state"]
|
||||
self.dependencies = parameters["dependencies"]
|
||||
self.pull = parameters["pull"]
|
||||
self.build = parameters["build"]
|
||||
self.ignore_build_events = parameters["ignore_build_events"]
|
||||
self.recreate = parameters["recreate"]
|
||||
self.remove_images = parameters["remove_images"]
|
||||
self.remove_volumes = parameters["remove_volumes"]
|
||||
self.remove_orphans = parameters["remove_orphans"]
|
||||
self.renew_anon_volumes = parameters["renew_anon_volumes"]
|
||||
self.timeout = parameters["timeout"]
|
||||
self.services = parameters["services"] or []
|
||||
self.scale = parameters["scale"] or {}
|
||||
self.wait = parameters["wait"]
|
||||
self.wait_timeout = parameters["wait_timeout"]
|
||||
self.yes = parameters["assume_yes"]
|
||||
self.state: t.Literal["absent", "present", "stopped", "restarted"] = parameters[
|
||||
"state"
|
||||
]
|
||||
self.dependencies: bool = parameters["dependencies"]
|
||||
self.pull: t.Literal["always", "missing", "never", "policy"] = parameters[
|
||||
"pull"
|
||||
]
|
||||
self.build: t.Literal["always", "never", "policy"] = parameters["build"]
|
||||
self.ignore_build_events: bool = parameters["ignore_build_events"]
|
||||
self.recreate: t.Literal["always", "never", "auto"] = parameters["recreate"]
|
||||
self.remove_images: t.Literal["all", "local"] | None = parameters[
|
||||
"remove_images"
|
||||
]
|
||||
self.remove_volumes: bool = parameters["remove_volumes"]
|
||||
self.remove_orphans: bool = parameters["remove_orphans"]
|
||||
self.renew_anon_volumes: bool = parameters["renew_anon_volumes"]
|
||||
self.timeout: int | None = parameters["timeout"]
|
||||
self.services: list[str] = parameters["services"] or []
|
||||
self.scale: dict[str, t.Any] = parameters["scale"] or {}
|
||||
self.wait: bool = parameters["wait"]
|
||||
self.wait_timeout: int | None = parameters["wait_timeout"]
|
||||
self.yes: bool = parameters["assume_yes"]
|
||||
if self.compose_version < LooseVersion("2.32.0") and self.yes:
|
||||
self.fail(
|
||||
f"assume_yes=true needs Docker Compose 2.32.0 or newer, not version {self.compose_version}"
|
||||
@@ -491,7 +498,7 @@ class ServicesManager(BaseComposeManager):
|
||||
self.fail(f"The value {value!r} for `scale[{key!r}]` is negative")
|
||||
self.scale[key] = value
|
||||
|
||||
def run(self):
|
||||
def run(self) -> dict[str, t.Any]:
|
||||
if self.state == "present":
|
||||
result = self.cmd_up()
|
||||
elif self.state == "stopped":
|
||||
@@ -508,7 +515,7 @@ class ServicesManager(BaseComposeManager):
|
||||
self.cleanup_result(result)
|
||||
return result
|
||||
|
||||
def get_up_cmd(self, dry_run, no_start=False):
|
||||
def get_up_cmd(self, dry_run: bool, no_start: bool = False) -> list[str]:
|
||||
args = self.get_base_args() + ["up", "--detach", "--no-color", "--quiet-pull"]
|
||||
if self.pull != "policy":
|
||||
args.extend(["--pull", self.pull])
|
||||
@@ -549,8 +556,8 @@ class ServicesManager(BaseComposeManager):
|
||||
args.append(service)
|
||||
return args
|
||||
|
||||
def cmd_up(self):
|
||||
result = {}
|
||||
def cmd_up(self) -> dict[str, t.Any]:
|
||||
result: dict[str, t.Any] = {}
|
||||
args = self.get_up_cmd(self.check_mode)
|
||||
rc, stdout, stderr = self.client.call_cli(*args, cwd=self.project_src)
|
||||
events = self.parse_events(stderr, dry_run=self.check_mode, nonzero_rc=rc != 0)
|
||||
@@ -566,7 +573,7 @@ class ServicesManager(BaseComposeManager):
|
||||
self.update_failed(result, events, args, stdout, stderr, rc)
|
||||
return result
|
||||
|
||||
def get_stop_cmd(self, dry_run):
|
||||
def get_stop_cmd(self, dry_run: bool) -> list[str]:
|
||||
args = self.get_base_args() + ["stop"]
|
||||
if self.timeout is not None:
|
||||
args.extend(["--timeout", f"{self.timeout}"])
|
||||
@@ -577,17 +584,17 @@ class ServicesManager(BaseComposeManager):
|
||||
args.append(service)
|
||||
return args
|
||||
|
||||
def _are_containers_stopped(self):
|
||||
def _are_containers_stopped(self) -> bool:
|
||||
for container in self.list_containers_raw():
|
||||
if container["State"] not in ("created", "exited", "stopped", "killed"):
|
||||
return False
|
||||
return True
|
||||
|
||||
def cmd_stop(self):
|
||||
def cmd_stop(self) -> dict[str, t.Any]:
|
||||
# Since 'docker compose stop' **always** claims it is stopping containers, even if they are already
|
||||
# stopped, we have to do this a bit more complicated.
|
||||
|
||||
result = {}
|
||||
result: dict[str, t.Any] = {}
|
||||
# Make sure all containers are created
|
||||
args_1 = self.get_up_cmd(self.check_mode, no_start=True)
|
||||
rc_1, stdout_1, stderr_1 = self.client.call_cli(*args_1, cwd=self.project_src)
|
||||
@@ -630,7 +637,7 @@ class ServicesManager(BaseComposeManager):
|
||||
)
|
||||
return result
|
||||
|
||||
def get_restart_cmd(self, dry_run):
|
||||
def get_restart_cmd(self, dry_run: bool) -> list[str]:
|
||||
args = self.get_base_args() + ["restart"]
|
||||
if not self.dependencies:
|
||||
args.append("--no-deps")
|
||||
@@ -643,8 +650,8 @@ class ServicesManager(BaseComposeManager):
|
||||
args.append(service)
|
||||
return args
|
||||
|
||||
def cmd_restart(self):
|
||||
result = {}
|
||||
def cmd_restart(self) -> dict[str, t.Any]:
|
||||
result: dict[str, t.Any] = {}
|
||||
args = self.get_restart_cmd(self.check_mode)
|
||||
rc, stdout, stderr = self.client.call_cli(*args, cwd=self.project_src)
|
||||
events = self.parse_events(stderr, dry_run=self.check_mode, nonzero_rc=rc != 0)
|
||||
@@ -653,7 +660,7 @@ class ServicesManager(BaseComposeManager):
|
||||
self.update_failed(result, events, args, stdout, stderr, rc)
|
||||
return result
|
||||
|
||||
def get_down_cmd(self, dry_run):
|
||||
def get_down_cmd(self, dry_run: bool) -> list[str]:
|
||||
args = self.get_base_args() + ["down"]
|
||||
if self.remove_orphans:
|
||||
args.append("--remove-orphans")
|
||||
@@ -670,8 +677,8 @@ class ServicesManager(BaseComposeManager):
|
||||
args.append(service)
|
||||
return args
|
||||
|
||||
def cmd_down(self):
|
||||
result = {}
|
||||
def cmd_down(self) -> dict[str, t.Any]:
|
||||
result: dict[str, t.Any] = {}
|
||||
args = self.get_down_cmd(self.check_mode)
|
||||
rc, stdout, stderr = self.client.call_cli(*args, cwd=self.project_src)
|
||||
events = self.parse_events(stderr, dry_run=self.check_mode, nonzero_rc=rc != 0)
|
||||
@@ -681,7 +688,7 @@ class ServicesManager(BaseComposeManager):
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"state": {
|
||||
"type": "str",
|
||||
|
||||
@@ -166,6 +166,7 @@ rc:
|
||||
|
||||
import shlex
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
|
||||
@@ -180,29 +181,32 @@ from ansible_collections.community.docker.plugins.module_utils._compose_v2 impor
|
||||
|
||||
|
||||
class ExecManager(BaseComposeManager):
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleModuleDockerClient) -> None:
|
||||
super().__init__(client)
|
||||
parameters = self.client.module.params
|
||||
|
||||
self.service = parameters["service"]
|
||||
self.index = parameters["index"]
|
||||
self.chdir = parameters["chdir"]
|
||||
self.detach = parameters["detach"]
|
||||
self.user = parameters["user"]
|
||||
self.stdin = parameters["stdin"]
|
||||
self.strip_empty_ends = parameters["strip_empty_ends"]
|
||||
self.privileged = parameters["privileged"]
|
||||
self.tty = parameters["tty"]
|
||||
self.env = parameters["env"]
|
||||
self.service: str = parameters["service"]
|
||||
self.index: int | None = parameters["index"]
|
||||
self.chdir: str | None = parameters["chdir"]
|
||||
self.detach: bool = parameters["detach"]
|
||||
self.user: str | None = parameters["user"]
|
||||
self.stdin: str | None = parameters["stdin"]
|
||||
self.strip_empty_ends: bool = parameters["strip_empty_ends"]
|
||||
self.privileged: bool = parameters["privileged"]
|
||||
self.tty: bool = parameters["tty"]
|
||||
self.env: dict[str, t.Any] = parameters["env"]
|
||||
|
||||
self.argv = parameters["argv"]
|
||||
self.argv: list[str]
|
||||
if parameters["command"] is not None:
|
||||
self.argv = shlex.split(parameters["command"])
|
||||
else:
|
||||
self.argv = parameters["argv"]
|
||||
|
||||
if self.detach and self.stdin is not None:
|
||||
self.fail("If detach=true, stdin cannot be provided.")
|
||||
|
||||
if self.stdin is not None and parameters["stdin_add_newline"]:
|
||||
stdin_add_newline: bool = parameters["stdin_add_newline"]
|
||||
if self.stdin is not None and stdin_add_newline:
|
||||
self.stdin += "\n"
|
||||
|
||||
if self.env is not None:
|
||||
@@ -214,7 +218,7 @@ class ExecManager(BaseComposeManager):
|
||||
)
|
||||
self.env[name] = to_text(value, errors="surrogate_or_strict")
|
||||
|
||||
def get_exec_cmd(self, dry_run, no_start=False):
|
||||
def get_exec_cmd(self, dry_run: bool) -> list[str]:
|
||||
args = self.get_base_args(plain_progress=True) + ["exec"]
|
||||
if self.index is not None:
|
||||
args.extend(["--index", str(self.index)])
|
||||
@@ -237,9 +241,9 @@ class ExecManager(BaseComposeManager):
|
||||
args.extend(self.argv)
|
||||
return args
|
||||
|
||||
def run(self):
|
||||
def run(self) -> dict[str, t.Any]:
|
||||
args = self.get_exec_cmd(self.check_mode)
|
||||
kwargs = {
|
||||
kwargs: dict[str, t.Any] = {
|
||||
"cwd": self.project_src,
|
||||
}
|
||||
if self.stdin is not None:
|
||||
@@ -262,7 +266,7 @@ class ExecManager(BaseComposeManager):
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"service": {"type": "str", "required": True},
|
||||
"index": {"type": "int"},
|
||||
|
||||
@@ -111,6 +111,7 @@ actions:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils._common_cli import (
|
||||
AnsibleModuleDockerClient,
|
||||
@@ -126,14 +127,14 @@ from ansible_collections.community.docker.plugins.module_utils._version import (
|
||||
|
||||
|
||||
class PullManager(BaseComposeManager):
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleModuleDockerClient) -> None:
|
||||
super().__init__(client)
|
||||
parameters = self.client.module.params
|
||||
|
||||
self.policy = parameters["policy"]
|
||||
self.ignore_buildable = parameters["ignore_buildable"]
|
||||
self.include_deps = parameters["include_deps"]
|
||||
self.services = parameters["services"] or []
|
||||
self.policy: t.Literal["always", "missing"] = parameters["policy"]
|
||||
self.ignore_buildable: bool = parameters["ignore_buildable"]
|
||||
self.include_deps: bool = parameters["include_deps"]
|
||||
self.services: list[str] = parameters["services"] or []
|
||||
|
||||
if self.policy != "always" and self.compose_version < LooseVersion("2.22.0"):
|
||||
# https://github.com/docker/compose/pull/10981 - 2.22.0
|
||||
@@ -146,7 +147,7 @@ class PullManager(BaseComposeManager):
|
||||
f"--ignore-buildable is only supported since Docker Compose 2.15.0. {self.client.get_cli()} has version {self.compose_version}"
|
||||
)
|
||||
|
||||
def get_pull_cmd(self, dry_run, no_start=False):
|
||||
def get_pull_cmd(self, dry_run: bool):
|
||||
args = self.get_base_args() + ["pull"]
|
||||
if self.policy != "always":
|
||||
args.extend(["--policy", self.policy])
|
||||
@@ -161,8 +162,8 @@ class PullManager(BaseComposeManager):
|
||||
args.append(service)
|
||||
return args
|
||||
|
||||
def run(self):
|
||||
result = {}
|
||||
def run(self) -> dict[str, t.Any]:
|
||||
result: dict[str, t.Any] = {}
|
||||
args = self.get_pull_cmd(self.check_mode)
|
||||
rc, stdout, stderr = self.client.call_cli(*args, cwd=self.project_src)
|
||||
events = self.parse_events(stderr, dry_run=self.check_mode, nonzero_rc=rc != 0)
|
||||
@@ -179,7 +180,7 @@ class PullManager(BaseComposeManager):
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"policy": {
|
||||
"type": "str",
|
||||
|
||||
@@ -239,6 +239,7 @@ rc:
|
||||
|
||||
import shlex
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
|
||||
@@ -253,42 +254,45 @@ from ansible_collections.community.docker.plugins.module_utils._compose_v2 impor
|
||||
|
||||
|
||||
class ExecManager(BaseComposeManager):
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleModuleDockerClient) -> None:
|
||||
super().__init__(client)
|
||||
parameters = self.client.module.params
|
||||
|
||||
self.service = parameters["service"]
|
||||
self.build = parameters["build"]
|
||||
self.cap_add = parameters["cap_add"]
|
||||
self.cap_drop = parameters["cap_drop"]
|
||||
self.entrypoint = parameters["entrypoint"]
|
||||
self.interactive = parameters["interactive"]
|
||||
self.labels = parameters["labels"]
|
||||
self.name = parameters["name"]
|
||||
self.no_deps = parameters["no_deps"]
|
||||
self.publish = parameters["publish"]
|
||||
self.quiet_pull = parameters["quiet_pull"]
|
||||
self.remove_orphans = parameters["remove_orphans"]
|
||||
self.do_cleanup = parameters["cleanup"]
|
||||
self.service_ports = parameters["service_ports"]
|
||||
self.use_aliases = parameters["use_aliases"]
|
||||
self.volumes = parameters["volumes"]
|
||||
self.chdir = parameters["chdir"]
|
||||
self.detach = parameters["detach"]
|
||||
self.user = parameters["user"]
|
||||
self.stdin = parameters["stdin"]
|
||||
self.strip_empty_ends = parameters["strip_empty_ends"]
|
||||
self.tty = parameters["tty"]
|
||||
self.env = parameters["env"]
|
||||
self.service: str = parameters["service"]
|
||||
self.build: bool = parameters["build"]
|
||||
self.cap_add: list[str] | None = parameters["cap_add"]
|
||||
self.cap_drop: list[str] | None = parameters["cap_drop"]
|
||||
self.entrypoint: str | None = parameters["entrypoint"]
|
||||
self.interactive: bool = parameters["interactive"]
|
||||
self.labels: list[str] | None = parameters["labels"]
|
||||
self.name: str | None = parameters["name"]
|
||||
self.no_deps: bool = parameters["no_deps"]
|
||||
self.publish: list[str] | None = parameters["publish"]
|
||||
self.quiet_pull: bool = parameters["quiet_pull"]
|
||||
self.remove_orphans: bool = parameters["remove_orphans"]
|
||||
self.do_cleanup: bool = parameters["cleanup"]
|
||||
self.service_ports: bool = parameters["service_ports"]
|
||||
self.use_aliases: bool = parameters["use_aliases"]
|
||||
self.volumes: list[str] | None = parameters["volumes"]
|
||||
self.chdir: str | None = parameters["chdir"]
|
||||
self.detach: bool = parameters["detach"]
|
||||
self.user: str | None = parameters["user"]
|
||||
self.stdin: str | None = parameters["stdin"]
|
||||
self.strip_empty_ends: bool = parameters["strip_empty_ends"]
|
||||
self.tty: bool = parameters["tty"]
|
||||
self.env: dict[str, t.Any] | None = parameters["env"]
|
||||
|
||||
self.argv = parameters["argv"]
|
||||
self.argv: list[str]
|
||||
if parameters["command"] is not None:
|
||||
self.argv = shlex.split(parameters["command"])
|
||||
else:
|
||||
self.argv = parameters["argv"]
|
||||
|
||||
if self.detach and self.stdin is not None:
|
||||
self.fail("If detach=true, stdin cannot be provided.")
|
||||
|
||||
if self.stdin is not None and parameters["stdin_add_newline"]:
|
||||
stdin_add_newline: bool = parameters["stdin_add_newline"]
|
||||
if self.stdin is not None and stdin_add_newline:
|
||||
self.stdin += "\n"
|
||||
|
||||
if self.env is not None:
|
||||
@@ -300,7 +304,7 @@ class ExecManager(BaseComposeManager):
|
||||
)
|
||||
self.env[name] = to_text(value, errors="surrogate_or_strict")
|
||||
|
||||
def get_run_cmd(self, dry_run, no_start=False):
|
||||
def get_run_cmd(self, dry_run: bool) -> list[str]:
|
||||
args = self.get_base_args(plain_progress=True) + ["run"]
|
||||
if self.build:
|
||||
args.append("--build")
|
||||
@@ -355,9 +359,9 @@ class ExecManager(BaseComposeManager):
|
||||
args.extend(self.argv)
|
||||
return args
|
||||
|
||||
def run(self):
|
||||
def run(self) -> dict[str, t.Any]:
|
||||
args = self.get_run_cmd(self.check_mode)
|
||||
kwargs = {
|
||||
kwargs: dict[str, t.Any] = {
|
||||
"cwd": self.project_src,
|
||||
}
|
||||
if self.stdin is not None:
|
||||
@@ -382,7 +386,7 @@ class ExecManager(BaseComposeManager):
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"service": {"type": "str", "required": True},
|
||||
"argv": {"type": "list", "elements": "str"},
|
||||
|
||||
@@ -1355,7 +1355,7 @@ from ansible_collections.community.docker.plugins.module_utils._module_container
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
engine_driver = DockerAPIEngineDriver()
|
||||
run_module(engine_driver)
|
||||
|
||||
|
||||
@@ -169,6 +169,7 @@ import io
|
||||
import os
|
||||
import stat
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||
from ansible.module_utils.common.validation import check_type_int
|
||||
@@ -198,23 +199,29 @@ from ansible_collections.community.docker.plugins.module_utils._scramble import
|
||||
)
|
||||
|
||||
|
||||
def are_fileobjs_equal(f1, f2):
|
||||
if t.TYPE_CHECKING:
|
||||
import tarfile
|
||||
|
||||
|
||||
def are_fileobjs_equal(f1: t.IO[bytes], f2: t.IO[bytes]) -> bool:
|
||||
"""Given two (buffered) file objects, compare their contents."""
|
||||
f1on: t.IO[bytes] | None = f1
|
||||
f2on: t.IO[bytes] | None = f2
|
||||
blocksize = 65536
|
||||
b1buf = b""
|
||||
b2buf = b""
|
||||
while True:
|
||||
if f1 and len(b1buf) < blocksize:
|
||||
f1b = f1.read(blocksize)
|
||||
if f1on and len(b1buf) < blocksize:
|
||||
f1b = f1on.read(blocksize)
|
||||
if not f1b:
|
||||
# f1 is EOF, so stop reading from it
|
||||
f1 = None
|
||||
f1on = None
|
||||
b1buf += f1b
|
||||
if f2 and len(b2buf) < blocksize:
|
||||
f2b = f2.read(blocksize)
|
||||
if f2on and len(b2buf) < blocksize:
|
||||
f2b = f2on.read(blocksize)
|
||||
if not f2b:
|
||||
# f2 is EOF, so stop reading from it
|
||||
f2 = None
|
||||
f2on = None
|
||||
b2buf += f2b
|
||||
if not b1buf or not b2buf:
|
||||
# At least one of f1 and f2 is EOF and all its data has
|
||||
@@ -229,29 +236,33 @@ def are_fileobjs_equal(f1, f2):
|
||||
b2buf = b2buf[buflen:]
|
||||
|
||||
|
||||
def are_fileobjs_equal_read_first(f1, f2):
|
||||
def are_fileobjs_equal_read_first(
|
||||
f1: t.IO[bytes], f2: t.IO[bytes]
|
||||
) -> tuple[bool, bytes]:
|
||||
"""Given two (buffered) file objects, compare their contents.
|
||||
|
||||
Returns a tuple (is_equal, content_of_f1), where the first element indicates
|
||||
whether the two file objects have the same content, and the second element is
|
||||
the content of the first file object."""
|
||||
f1on: t.IO[bytes] | None = f1
|
||||
f2on: t.IO[bytes] | None = f2
|
||||
blocksize = 65536
|
||||
b1buf = b""
|
||||
b2buf = b""
|
||||
is_equal = True
|
||||
content = []
|
||||
while True:
|
||||
if f1 and len(b1buf) < blocksize:
|
||||
f1b = f1.read(blocksize)
|
||||
if f1on and len(b1buf) < blocksize:
|
||||
f1b = f1on.read(blocksize)
|
||||
if not f1b:
|
||||
# f1 is EOF, so stop reading from it
|
||||
f1 = None
|
||||
f1on = None
|
||||
b1buf += f1b
|
||||
if f2 and len(b2buf) < blocksize:
|
||||
f2b = f2.read(blocksize)
|
||||
if f2on and len(b2buf) < blocksize:
|
||||
f2b = f2on.read(blocksize)
|
||||
if not f2b:
|
||||
# f2 is EOF, so stop reading from it
|
||||
f2 = None
|
||||
f2on = None
|
||||
b2buf += f2b
|
||||
if not b1buf or not b2buf:
|
||||
# At least one of f1 and f2 is EOF and all its data has
|
||||
@@ -269,13 +280,13 @@ def are_fileobjs_equal_read_first(f1, f2):
|
||||
b2buf = b2buf[buflen:]
|
||||
|
||||
content.append(b1buf)
|
||||
if f1:
|
||||
content.append(f1.read())
|
||||
if f1on:
|
||||
content.append(f1on.read())
|
||||
|
||||
return is_equal, b"".join(content)
|
||||
|
||||
|
||||
def is_container_file_not_regular_file(container_stat):
|
||||
def is_container_file_not_regular_file(container_stat: dict[str, t.Any]) -> bool:
|
||||
for bit in (
|
||||
# https://pkg.go.dev/io/fs#FileMode
|
||||
32 - 1, # ModeDir
|
||||
@@ -292,7 +303,7 @@ def is_container_file_not_regular_file(container_stat):
|
||||
return False
|
||||
|
||||
|
||||
def get_container_file_mode(container_stat):
|
||||
def get_container_file_mode(container_stat: dict[str, t.Any]) -> int:
|
||||
mode = container_stat["mode"] & 0xFFF
|
||||
if container_stat["mode"] & (1 << (32 - 9)) != 0: # ModeSetuid
|
||||
mode |= stat.S_ISUID # set UID bit
|
||||
@@ -303,7 +314,9 @@ def get_container_file_mode(container_stat):
|
||||
return mode
|
||||
|
||||
|
||||
def add_other_diff(diff, in_path, member):
|
||||
def add_other_diff(
|
||||
diff: dict[str, t.Any] | None, in_path: str, member: tarfile.TarInfo
|
||||
) -> None:
|
||||
if diff is None:
|
||||
return
|
||||
diff["before_header"] = in_path
|
||||
@@ -326,14 +339,14 @@ def add_other_diff(diff, in_path, member):
|
||||
|
||||
|
||||
def retrieve_diff(
|
||||
client,
|
||||
container,
|
||||
container_path,
|
||||
follow_links,
|
||||
diff,
|
||||
max_file_size_for_diff,
|
||||
regular_stat=None,
|
||||
link_target=None,
|
||||
client: AnsibleDockerClient,
|
||||
container: str,
|
||||
container_path: str,
|
||||
follow_links: bool,
|
||||
diff: dict[str, t.Any] | None,
|
||||
max_file_size_for_diff: int,
|
||||
regular_stat: dict[str, t.Any] | None = None,
|
||||
link_target: str | None = None,
|
||||
):
|
||||
if diff is None:
|
||||
return
|
||||
@@ -377,19 +390,21 @@ def retrieve_diff(
|
||||
return
|
||||
|
||||
# We need to get hold of the content
|
||||
def process_none(in_path):
|
||||
def process_none(in_path: str) -> None:
|
||||
diff["before"] = ""
|
||||
|
||||
def process_regular(in_path, tar, member):
|
||||
def process_regular(
|
||||
in_path: str, tar: tarfile.TarFile, member: tarfile.TarInfo
|
||||
) -> None:
|
||||
add_diff_dst_from_regular_member(
|
||||
diff, max_file_size_for_diff, in_path, tar, member
|
||||
)
|
||||
|
||||
def process_symlink(in_path, member):
|
||||
def process_symlink(in_path: str, member: tarfile.TarInfo) -> None:
|
||||
diff["before_header"] = in_path
|
||||
diff["before"] = member.linkname
|
||||
|
||||
def process_other(in_path, member):
|
||||
def process_other(in_path: str, member: tarfile.TarInfo) -> None:
|
||||
add_other_diff(diff, in_path, member)
|
||||
|
||||
fetch_file_ex(
|
||||
@@ -404,7 +419,7 @@ def retrieve_diff(
|
||||
)
|
||||
|
||||
|
||||
def is_binary(content):
|
||||
def is_binary(content: bytes) -> bool:
|
||||
if b"\x00" in content:
|
||||
return True
|
||||
# TODO: better detection
|
||||
@@ -413,8 +428,13 @@ def is_binary(content):
|
||||
|
||||
|
||||
def are_fileobjs_equal_with_diff_of_first(
|
||||
f1, f2, size, diff, max_file_size_for_diff, container_path
|
||||
):
|
||||
f1: t.IO[bytes],
|
||||
f2: t.IO[bytes],
|
||||
size: int,
|
||||
diff: dict[str, t.Any] | None,
|
||||
max_file_size_for_diff: int,
|
||||
container_path: str,
|
||||
) -> bool:
|
||||
if diff is None:
|
||||
return are_fileobjs_equal(f1, f2)
|
||||
if size > max_file_size_for_diff > 0:
|
||||
@@ -430,15 +450,22 @@ def are_fileobjs_equal_with_diff_of_first(
|
||||
|
||||
|
||||
def add_diff_dst_from_regular_member(
|
||||
diff, max_file_size_for_diff, container_path, tar, member
|
||||
):
|
||||
diff: dict[str, t.Any] | None,
|
||||
max_file_size_for_diff: int,
|
||||
container_path: str,
|
||||
tar: tarfile.TarFile,
|
||||
member: tarfile.TarInfo,
|
||||
) -> None:
|
||||
if diff is None:
|
||||
return
|
||||
if member.size > max_file_size_for_diff > 0:
|
||||
diff["dst_larger"] = max_file_size_for_diff
|
||||
return
|
||||
|
||||
with tar.extractfile(member) as tar_f:
|
||||
mf = tar.extractfile(member)
|
||||
if not mf:
|
||||
raise AssertionError("Member should be present for regular file")
|
||||
with mf as tar_f:
|
||||
content = tar_f.read()
|
||||
|
||||
if is_binary(content):
|
||||
@@ -448,35 +475,35 @@ def add_diff_dst_from_regular_member(
|
||||
diff["before"] = to_text(content)
|
||||
|
||||
|
||||
def copy_dst_to_src(diff):
|
||||
def copy_dst_to_src(diff: dict[str, t.Any] | None) -> None:
|
||||
if diff is None:
|
||||
return
|
||||
for f, t in [
|
||||
for frm, to in [
|
||||
("dst_size", "src_size"),
|
||||
("dst_binary", "src_binary"),
|
||||
("before_header", "after_header"),
|
||||
("before", "after"),
|
||||
]:
|
||||
if f in diff:
|
||||
diff[t] = diff[f]
|
||||
elif t in diff:
|
||||
diff.pop(t)
|
||||
if frm in diff:
|
||||
diff[to] = diff[frm]
|
||||
elif to in diff:
|
||||
diff.pop(to)
|
||||
|
||||
|
||||
def is_file_idempotent(
|
||||
client,
|
||||
container,
|
||||
managed_path,
|
||||
container_path,
|
||||
follow_links,
|
||||
local_follow_links,
|
||||
client: AnsibleDockerClient,
|
||||
container: str,
|
||||
managed_path: str,
|
||||
container_path: str,
|
||||
follow_links: bool,
|
||||
local_follow_links: bool,
|
||||
owner_id,
|
||||
group_id,
|
||||
mode,
|
||||
force=False,
|
||||
diff=None,
|
||||
max_file_size_for_diff=1,
|
||||
):
|
||||
force: bool | None = False,
|
||||
diff: dict[str, t.Any] | None = None,
|
||||
max_file_size_for_diff: int = 1,
|
||||
) -> tuple[str, int, bool]:
|
||||
# Retrieve information of local file
|
||||
try:
|
||||
file_stat = (
|
||||
@@ -644,10 +671,12 @@ def is_file_idempotent(
|
||||
return container_path, mode, False
|
||||
|
||||
# Fetch file from container
|
||||
def process_none(in_path):
|
||||
def process_none(in_path: str) -> tuple[str, int, bool]:
|
||||
return container_path, mode, False
|
||||
|
||||
def process_regular(in_path, tar, member):
|
||||
def process_regular(
|
||||
in_path: str, tar: tarfile.TarFile, member: tarfile.TarInfo
|
||||
) -> tuple[str, int, bool]:
|
||||
# Check things like user/group ID and mode
|
||||
if any(
|
||||
[
|
||||
@@ -663,14 +692,17 @@ def is_file_idempotent(
|
||||
)
|
||||
return container_path, mode, False
|
||||
|
||||
with tar.extractfile(member) as tar_f:
|
||||
mf = tar.extractfile(member)
|
||||
if mf is None:
|
||||
raise AssertionError("Member should be present for regular file")
|
||||
with mf as tar_f:
|
||||
with open(managed_path, "rb") as local_f:
|
||||
is_equal = are_fileobjs_equal_with_diff_of_first(
|
||||
tar_f, local_f, member.size, diff, max_file_size_for_diff, in_path
|
||||
)
|
||||
return container_path, mode, is_equal
|
||||
|
||||
def process_symlink(in_path, member):
|
||||
def process_symlink(in_path: str, member: tarfile.TarInfo) -> tuple[str, int, bool]:
|
||||
if diff is not None:
|
||||
diff["before_header"] = in_path
|
||||
diff["before"] = member.linkname
|
||||
@@ -689,7 +721,7 @@ def is_file_idempotent(
|
||||
local_link_target = os.readlink(managed_path)
|
||||
return container_path, mode, member.linkname == local_link_target
|
||||
|
||||
def process_other(in_path, member):
|
||||
def process_other(in_path: str, member: tarfile.TarInfo) -> tuple[str, int, bool]:
|
||||
add_other_diff(diff, in_path, member)
|
||||
return container_path, mode, False
|
||||
|
||||
@@ -706,23 +738,21 @@ def is_file_idempotent(
|
||||
|
||||
|
||||
def copy_file_into_container(
|
||||
client,
|
||||
container,
|
||||
managed_path,
|
||||
container_path,
|
||||
follow_links,
|
||||
local_follow_links,
|
||||
client: AnsibleDockerClient,
|
||||
container: str,
|
||||
managed_path: str,
|
||||
container_path: str,
|
||||
follow_links: bool,
|
||||
local_follow_links: bool,
|
||||
owner_id,
|
||||
group_id,
|
||||
mode,
|
||||
force=False,
|
||||
diff=False,
|
||||
max_file_size_for_diff=1,
|
||||
):
|
||||
if diff:
|
||||
diff = {}
|
||||
else:
|
||||
diff = None
|
||||
force: bool | None = False,
|
||||
do_diff: bool = False,
|
||||
max_file_size_for_diff: int = 1,
|
||||
) -> t.NoReturn:
|
||||
diff: dict[str, t.Any] | None
|
||||
diff = {} if do_diff else None
|
||||
|
||||
container_path, mode, idempotent = is_file_idempotent(
|
||||
client,
|
||||
@@ -762,18 +792,18 @@ def copy_file_into_container(
|
||||
|
||||
|
||||
def is_content_idempotent(
|
||||
client,
|
||||
container,
|
||||
content,
|
||||
container_path,
|
||||
follow_links,
|
||||
client: AnsibleDockerClient,
|
||||
container: str,
|
||||
content: bytes,
|
||||
container_path: str,
|
||||
follow_links: bool,
|
||||
owner_id,
|
||||
group_id,
|
||||
mode,
|
||||
force=False,
|
||||
diff=None,
|
||||
max_file_size_for_diff=1,
|
||||
):
|
||||
force: bool | None = False,
|
||||
diff: dict[str, t.Any] | None = None,
|
||||
max_file_size_for_diff: int = 1,
|
||||
) -> tuple[str, int, bool]:
|
||||
if diff is not None:
|
||||
if len(content) > max_file_size_for_diff > 0:
|
||||
diff["src_larger"] = max_file_size_for_diff
|
||||
@@ -894,12 +924,14 @@ def is_content_idempotent(
|
||||
return container_path, mode, False
|
||||
|
||||
# Fetch file from container
|
||||
def process_none(in_path):
|
||||
def process_none(in_path: str) -> tuple[str, int, bool]:
|
||||
if diff is not None:
|
||||
diff["before"] = ""
|
||||
return container_path, mode, False
|
||||
|
||||
def process_regular(in_path, tar, member):
|
||||
def process_regular(
|
||||
in_path: str, tar: tarfile.TarFile, member: tarfile.TarInfo
|
||||
) -> tuple[str, int, bool]:
|
||||
# Check things like user/group ID and mode
|
||||
if any(
|
||||
[
|
||||
@@ -914,7 +946,10 @@ def is_content_idempotent(
|
||||
)
|
||||
return container_path, mode, False
|
||||
|
||||
with tar.extractfile(member) as tar_f:
|
||||
mf = tar.extractfile(member)
|
||||
if mf is None:
|
||||
raise AssertionError("Member should be present for regular file")
|
||||
with mf as tar_f:
|
||||
is_equal = are_fileobjs_equal_with_diff_of_first(
|
||||
tar_f,
|
||||
io.BytesIO(content),
|
||||
@@ -925,14 +960,14 @@ def is_content_idempotent(
|
||||
)
|
||||
return container_path, mode, is_equal
|
||||
|
||||
def process_symlink(in_path, member):
|
||||
def process_symlink(in_path: str, member: tarfile.TarInfo) -> tuple[str, int, bool]:
|
||||
if diff is not None:
|
||||
diff["before_header"] = in_path
|
||||
diff["before"] = member.linkname
|
||||
|
||||
return container_path, mode, False
|
||||
|
||||
def process_other(in_path, member):
|
||||
def process_other(in_path: str, member: tarfile.TarInfo) -> tuple[str, int, bool]:
|
||||
add_other_diff(diff, in_path, member)
|
||||
return container_path, mode, False
|
||||
|
||||
@@ -949,22 +984,19 @@ def is_content_idempotent(
|
||||
|
||||
|
||||
def copy_content_into_container(
|
||||
client,
|
||||
container,
|
||||
content,
|
||||
container_path,
|
||||
follow_links,
|
||||
client: AnsibleDockerClient,
|
||||
container: str,
|
||||
content: bytes,
|
||||
container_path: str,
|
||||
follow_links: bool,
|
||||
owner_id,
|
||||
group_id,
|
||||
mode,
|
||||
force=False,
|
||||
diff=False,
|
||||
max_file_size_for_diff=1,
|
||||
):
|
||||
if diff:
|
||||
diff = {}
|
||||
else:
|
||||
diff = None
|
||||
force: bool | None = False,
|
||||
do_diff: bool = False,
|
||||
max_file_size_for_diff: int = 1,
|
||||
) -> t.NoReturn:
|
||||
diff: dict[str, t.Any] | None = {} if do_diff else None
|
||||
|
||||
container_path, mode, idempotent = is_content_idempotent(
|
||||
client,
|
||||
@@ -1007,7 +1039,7 @@ def copy_content_into_container(
|
||||
client.module.exit_json(**result)
|
||||
|
||||
|
||||
def parse_modern(mode):
|
||||
def parse_modern(mode: str | int) -> int:
|
||||
if isinstance(mode, str):
|
||||
return int(to_native(mode), 8)
|
||||
if isinstance(mode, int):
|
||||
@@ -1015,13 +1047,13 @@ def parse_modern(mode):
|
||||
raise TypeError(f"must be an octal string or an integer, got {mode!r}")
|
||||
|
||||
|
||||
def parse_octal_string_only(mode):
|
||||
def parse_octal_string_only(mode: str) -> int:
|
||||
if isinstance(mode, str):
|
||||
return int(to_native(mode), 8)
|
||||
raise TypeError(f"must be an octal string, got {mode!r}")
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"container": {"type": "str", "required": True},
|
||||
"path": {"type": "path"},
|
||||
@@ -1054,20 +1086,22 @@ def main():
|
||||
},
|
||||
)
|
||||
|
||||
container = client.module.params["container"]
|
||||
managed_path = client.module.params["path"]
|
||||
container_path = client.module.params["container_path"]
|
||||
follow = client.module.params["follow"]
|
||||
local_follow = client.module.params["local_follow"]
|
||||
owner_id = client.module.params["owner_id"]
|
||||
group_id = client.module.params["group_id"]
|
||||
mode = client.module.params["mode"]
|
||||
force = client.module.params["force"]
|
||||
content = client.module.params["content"]
|
||||
max_file_size_for_diff = client.module.params["_max_file_size_for_diff"] or 1
|
||||
container: str = client.module.params["container"]
|
||||
managed_path: str | None = client.module.params["path"]
|
||||
container_path: str = client.module.params["container_path"]
|
||||
follow: bool = client.module.params["follow"]
|
||||
local_follow: bool = client.module.params["local_follow"]
|
||||
owner_id: int | None = client.module.params["owner_id"]
|
||||
group_id: int | None = client.module.params["group_id"]
|
||||
mode: t.Any = client.module.params["mode"]
|
||||
force: bool | None = client.module.params["force"]
|
||||
content_str: str | None = client.module.params["content"]
|
||||
max_file_size_for_diff: int = client.module.params["_max_file_size_for_diff"] or 1
|
||||
|
||||
if mode is not None:
|
||||
mode_parse = client.module.params["mode_parse"]
|
||||
mode_parse: t.Literal["legacy", "modern", "octal_string_only"] = (
|
||||
client.module.params["mode_parse"]
|
||||
)
|
||||
try:
|
||||
if mode_parse == "legacy":
|
||||
mode = check_type_int(mode)
|
||||
@@ -1080,14 +1114,15 @@ def main():
|
||||
if mode < 0:
|
||||
client.fail(f"'mode' must not be negative; got {mode}")
|
||||
|
||||
if content is not None:
|
||||
content: bytes | None = None
|
||||
if content_str is not None:
|
||||
if client.module.params["content_is_b64"]:
|
||||
try:
|
||||
content = base64.b64decode(content)
|
||||
content = base64.b64decode(content_str)
|
||||
except Exception as e: # pylint: disable=broad-exception-caught
|
||||
client.fail(f"Cannot Base64 decode the content option: {e}")
|
||||
else:
|
||||
content = to_bytes(content)
|
||||
content = to_bytes(content_str)
|
||||
|
||||
if not container_path.startswith(os.path.sep):
|
||||
container_path = os.path.join(os.path.sep, container_path)
|
||||
@@ -1108,7 +1143,7 @@ def main():
|
||||
group_id=group_id,
|
||||
mode=mode,
|
||||
force=force,
|
||||
diff=client.module._diff,
|
||||
do_diff=client.module._diff,
|
||||
max_file_size_for_diff=max_file_size_for_diff,
|
||||
)
|
||||
elif managed_path is not None:
|
||||
@@ -1123,7 +1158,7 @@ def main():
|
||||
group_id=group_id,
|
||||
mode=mode,
|
||||
force=force,
|
||||
diff=client.module._diff,
|
||||
do_diff=client.module._diff,
|
||||
max_file_size_for_diff=max_file_size_for_diff,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -165,6 +165,7 @@ exec_id:
|
||||
|
||||
import shlex
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
|
||||
@@ -185,7 +186,7 @@ from ansible_collections.community.docker.plugins.module_utils._socket_handler i
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"container": {"type": "str", "required": True},
|
||||
"argv": {"type": "list", "elements": "str"},
|
||||
@@ -211,16 +212,16 @@ def main():
|
||||
required_one_of=[("argv", "command")],
|
||||
)
|
||||
|
||||
container = client.module.params["container"]
|
||||
argv = client.module.params["argv"]
|
||||
command = client.module.params["command"]
|
||||
chdir = client.module.params["chdir"]
|
||||
detach = client.module.params["detach"]
|
||||
user = client.module.params["user"]
|
||||
stdin = client.module.params["stdin"]
|
||||
strip_empty_ends = client.module.params["strip_empty_ends"]
|
||||
tty = client.module.params["tty"]
|
||||
env = client.module.params["env"]
|
||||
container: str = client.module.params["container"]
|
||||
argv: list[str] | None = client.module.params["argv"]
|
||||
command: str | None = client.module.params["command"]
|
||||
chdir: str | None = client.module.params["chdir"]
|
||||
detach: bool = client.module.params["detach"]
|
||||
user: str | None = client.module.params["user"]
|
||||
stdin: str | None = client.module.params["stdin"]
|
||||
strip_empty_ends: bool = client.module.params["strip_empty_ends"]
|
||||
tty: bool = client.module.params["tty"]
|
||||
env: dict[str, t.Any] = client.module.params["env"]
|
||||
|
||||
if env is not None:
|
||||
for name, value in list(env.items()):
|
||||
@@ -233,6 +234,7 @@ def main():
|
||||
|
||||
if command is not None:
|
||||
argv = shlex.split(command)
|
||||
assert argv is not None
|
||||
|
||||
if detach and stdin is not None:
|
||||
client.module.fail_json(msg="If detach=true, stdin cannot be provided.")
|
||||
@@ -258,7 +260,7 @@ def main():
|
||||
exec_data = client.post_json_to_json(
|
||||
"/containers/{0}/exec", container, data=data
|
||||
)
|
||||
exec_id = exec_data["Id"]
|
||||
exec_id: str = exec_data["Id"]
|
||||
|
||||
data = {
|
||||
"Tty": tty,
|
||||
@@ -269,6 +271,8 @@ def main():
|
||||
client.module.exit_json(changed=True, exec_id=exec_id)
|
||||
|
||||
else:
|
||||
stdout: bytes | None
|
||||
stderr: bytes | None
|
||||
if stdin and not detach:
|
||||
exec_socket = client.post_json_to_stream_socket(
|
||||
"/exec/{0}/start", exec_id, data=data
|
||||
@@ -283,28 +287,37 @@ def main():
|
||||
stdout, stderr = exec_socket_handler.consume()
|
||||
finally:
|
||||
exec_socket.close()
|
||||
elif tty:
|
||||
stdout, stderr = client.post_json_to_stream(
|
||||
"/exec/{0}/start",
|
||||
exec_id,
|
||||
data=data,
|
||||
stream=False,
|
||||
tty=True,
|
||||
demux=True,
|
||||
)
|
||||
else:
|
||||
stdout, stderr = client.post_json_to_stream(
|
||||
"/exec/{0}/start",
|
||||
exec_id,
|
||||
data=data,
|
||||
stream=False,
|
||||
tty=tty,
|
||||
tty=False,
|
||||
demux=True,
|
||||
)
|
||||
|
||||
result = client.get_json("/exec/{0}/json", exec_id)
|
||||
|
||||
stdout = to_text(stdout or b"")
|
||||
stderr = to_text(stderr or b"")
|
||||
stdout_t = to_text(stdout or b"")
|
||||
stderr_t = to_text(stderr or b"")
|
||||
if strip_empty_ends:
|
||||
stdout = stdout.rstrip("\r\n")
|
||||
stderr = stderr.rstrip("\r\n")
|
||||
stdout_t = stdout_t.rstrip("\r\n")
|
||||
stderr_t = stderr_t.rstrip("\r\n")
|
||||
|
||||
client.module.exit_json(
|
||||
changed=True,
|
||||
stdout=stdout,
|
||||
stderr=stderr,
|
||||
stdout=stdout_t,
|
||||
stderr=stderr_t,
|
||||
rc=result.get("ExitCode") or 0,
|
||||
)
|
||||
except NotFound:
|
||||
|
||||
@@ -86,7 +86,7 @@ from ansible_collections.community.docker.plugins.module_utils._common_api impor
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"name": {"type": "str", "required": True},
|
||||
}
|
||||
@@ -96,8 +96,9 @@ def main():
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
container_id: str = client.module.params["name"]
|
||||
try:
|
||||
container = client.get_container(client.module.params["name"])
|
||||
container = client.get_container(container_id)
|
||||
|
||||
client.module.exit_json(
|
||||
changed=False,
|
||||
|
||||
@@ -173,6 +173,7 @@ current_context_name:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
@@ -185,6 +186,7 @@ from ansible_collections.community.docker.plugins.module_utils._api.context.conf
|
||||
)
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.context.context import (
|
||||
IN_MEMORY,
|
||||
Context,
|
||||
)
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.errors import (
|
||||
ContextException,
|
||||
@@ -192,7 +194,13 @@ from ansible_collections.community.docker.plugins.module_utils._api.errors impor
|
||||
)
|
||||
|
||||
|
||||
def tls_context_to_json(context):
|
||||
if t.TYPE_CHECKING:
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.tls import (
|
||||
TLSConfig,
|
||||
)
|
||||
|
||||
|
||||
def tls_context_to_json(context: TLSConfig | None) -> dict[str, t.Any] | None:
|
||||
if context is None:
|
||||
return None
|
||||
return {
|
||||
@@ -204,8 +212,8 @@ def tls_context_to_json(context):
|
||||
}
|
||||
|
||||
|
||||
def context_to_json(context, current):
|
||||
module_config = {}
|
||||
def context_to_json(context: Context, current: bool) -> dict[str, t.Any]:
|
||||
module_config: dict[str, t.Any] = {}
|
||||
if "docker" in context.endpoints:
|
||||
endpoint = context.endpoints["docker"]
|
||||
if isinstance(endpoint.get("Host"), str):
|
||||
@@ -247,7 +255,7 @@ def context_to_json(context, current):
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"only_current": {"type": "bool", "default": False},
|
||||
"name": {"type": "str"},
|
||||
@@ -262,28 +270,31 @@ def main():
|
||||
],
|
||||
)
|
||||
|
||||
only_current: bool = module.params["only_current"]
|
||||
name: str | None = module.params["name"]
|
||||
cli_context: str | None = module.params["cli_context"]
|
||||
try:
|
||||
if module.params["cli_context"]:
|
||||
if cli_context:
|
||||
current_context_name, current_context_source = (
|
||||
module.params["cli_context"],
|
||||
cli_context,
|
||||
"cli_context module option",
|
||||
)
|
||||
else:
|
||||
current_context_name, current_context_source = (
|
||||
get_current_context_name_with_source()
|
||||
)
|
||||
if module.params["name"]:
|
||||
contexts = [ContextAPI.get_context(module.params["name"])]
|
||||
if not contexts[0]:
|
||||
module.fail_json(
|
||||
msg=f"There is no context of name {module.params['name']!r}"
|
||||
)
|
||||
elif module.params["only_current"]:
|
||||
contexts = [ContextAPI.get_context(current_context_name)]
|
||||
if not contexts[0]:
|
||||
if name:
|
||||
context_or_none = ContextAPI.get_context(name)
|
||||
if not context_or_none:
|
||||
module.fail_json(msg=f"There is no context of name {name!r}")
|
||||
contexts = [context_or_none]
|
||||
elif only_current:
|
||||
context_or_none = ContextAPI.get_context(current_context_name)
|
||||
if not context_or_none:
|
||||
module.fail_json(
|
||||
msg=f"There is no context of name {current_context_name!r}, which is configured as the default context ({current_context_source})",
|
||||
)
|
||||
contexts = [context_or_none]
|
||||
else:
|
||||
contexts = ContextAPI.contexts()
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ disk_usage:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.errors import (
|
||||
APIError,
|
||||
@@ -231,9 +232,7 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
|
||||
|
||||
class DockerHostManager(DockerBaseClass):
|
||||
|
||||
def __init__(self, client, results):
|
||||
|
||||
def __init__(self, client: AnsibleDockerClient, results: dict[str, t.Any]) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.client = client
|
||||
@@ -253,21 +252,21 @@ class DockerHostManager(DockerBaseClass):
|
||||
for docker_object in listed_objects:
|
||||
if self.client.module.params[docker_object]:
|
||||
returned_name = docker_object
|
||||
filter_name = docker_object + "_filters"
|
||||
filter_name = f"{docker_object}_filters"
|
||||
filters = clean_dict_booleans_for_docker_api(
|
||||
client.module.params.get(filter_name), True
|
||||
client.module.params.get(filter_name), allow_sequences=True
|
||||
)
|
||||
self.results[returned_name] = self.get_docker_items_list(
|
||||
docker_object, filters
|
||||
)
|
||||
|
||||
def get_docker_host_info(self):
|
||||
def get_docker_host_info(self) -> dict[str, t.Any]:
|
||||
try:
|
||||
return self.client.info()
|
||||
except APIError as exc:
|
||||
self.client.fail(f"Error inspecting docker host: {exc}")
|
||||
|
||||
def get_docker_disk_usage_facts(self):
|
||||
def get_docker_disk_usage_facts(self) -> dict[str, t.Any]:
|
||||
try:
|
||||
if self.verbose_output:
|
||||
return self.client.df()
|
||||
@@ -275,9 +274,13 @@ class DockerHostManager(DockerBaseClass):
|
||||
except APIError as exc:
|
||||
self.client.fail(f"Error inspecting docker host: {exc}")
|
||||
|
||||
def get_docker_items_list(self, docker_object=None, filters=None, verbose=False):
|
||||
items = None
|
||||
items_list = []
|
||||
def get_docker_items_list(
|
||||
self,
|
||||
docker_object: str,
|
||||
filters: dict[str, t.Any] | None = None,
|
||||
verbose: bool = False,
|
||||
) -> list[dict[str, t.Any]]:
|
||||
items = []
|
||||
|
||||
header_containers = [
|
||||
"Id",
|
||||
@@ -329,6 +332,7 @@ class DockerHostManager(DockerBaseClass):
|
||||
if self.verbose_output:
|
||||
return items
|
||||
|
||||
items_list = []
|
||||
for item in items:
|
||||
item_record = {}
|
||||
|
||||
@@ -349,7 +353,7 @@ class DockerHostManager(DockerBaseClass):
|
||||
return items_list
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"containers": {"type": "bool", "default": False},
|
||||
"containers_all": {"type": "bool", "default": False},
|
||||
|
||||
@@ -367,6 +367,7 @@ import errno
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.module_utils.common.text.formatters import human_to_bytes
|
||||
@@ -411,7 +412,18 @@ from ansible_collections.community.docker.plugins.module_utils._version import (
|
||||
)
|
||||
|
||||
|
||||
def convert_to_bytes(value, module, name, unlimited_value=None):
|
||||
if t.TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def convert_to_bytes(
|
||||
value: str | None,
|
||||
module: AnsibleModule,
|
||||
name: str,
|
||||
unlimited_value: int | None = None,
|
||||
) -> int | None:
|
||||
if value is None:
|
||||
return value
|
||||
try:
|
||||
@@ -423,8 +435,7 @@ def convert_to_bytes(value, module, name, unlimited_value=None):
|
||||
|
||||
|
||||
class ImageManager(DockerBaseClass):
|
||||
|
||||
def __init__(self, client, results):
|
||||
def __init__(self, client: AnsibleDockerClient, results: dict[str, t.Any]) -> None:
|
||||
"""
|
||||
Configure a docker_image task.
|
||||
|
||||
@@ -441,12 +452,14 @@ class ImageManager(DockerBaseClass):
|
||||
parameters = self.client.module.params
|
||||
self.check_mode = self.client.check_mode
|
||||
|
||||
self.source = parameters["source"]
|
||||
build = parameters["build"] or {}
|
||||
pull = parameters["pull"] or {}
|
||||
self.archive_path = parameters["archive_path"]
|
||||
self.cache_from = build.get("cache_from")
|
||||
self.container_limits = build.get("container_limits")
|
||||
self.source: t.Literal["build", "load", "pull", "local"] | None = parameters[
|
||||
"source"
|
||||
]
|
||||
build: dict[str, t.Any] = parameters["build"] or {}
|
||||
pull: dict[str, t.Any] = parameters["pull"] or {}
|
||||
self.archive_path: str | None = parameters["archive_path"]
|
||||
self.cache_from: list[str] | None = build.get("cache_from")
|
||||
self.container_limits: dict[str, t.Any] | None = build.get("container_limits")
|
||||
if self.container_limits and "memory" in self.container_limits:
|
||||
self.container_limits["memory"] = convert_to_bytes(
|
||||
self.container_limits["memory"],
|
||||
@@ -460,32 +473,36 @@ class ImageManager(DockerBaseClass):
|
||||
"build.container_limits.memswap",
|
||||
unlimited_value=-1,
|
||||
)
|
||||
self.dockerfile = build.get("dockerfile")
|
||||
self.force_source = parameters["force_source"]
|
||||
self.force_absent = parameters["force_absent"]
|
||||
self.force_tag = parameters["force_tag"]
|
||||
self.load_path = parameters["load_path"]
|
||||
self.name = parameters["name"]
|
||||
self.network = build.get("network")
|
||||
self.extra_hosts = clean_dict_booleans_for_docker_api(build.get("etc_hosts"))
|
||||
self.nocache = build.get("nocache", False)
|
||||
self.build_path = build.get("path")
|
||||
self.pull = build.get("pull")
|
||||
self.target = build.get("target")
|
||||
self.repository = parameters["repository"]
|
||||
self.rm = build.get("rm", True)
|
||||
self.state = parameters["state"]
|
||||
self.tag = parameters["tag"]
|
||||
self.http_timeout = build.get("http_timeout")
|
||||
self.pull_platform = pull.get("platform")
|
||||
self.push = parameters["push"]
|
||||
self.buildargs = build.get("args")
|
||||
self.build_platform = build.get("platform")
|
||||
self.use_config_proxy = build.get("use_config_proxy")
|
||||
self.shm_size = convert_to_bytes(
|
||||
self.dockerfile: str | None = build.get("dockerfile")
|
||||
self.force_source: bool = parameters["force_source"]
|
||||
self.force_absent: bool = parameters["force_absent"]
|
||||
self.force_tag: bool = parameters["force_tag"]
|
||||
self.load_path: str | None = parameters["load_path"]
|
||||
self.name: str = parameters["name"]
|
||||
self.network: str | None = build.get("network")
|
||||
self.extra_hosts: dict[str, str] = clean_dict_booleans_for_docker_api(
|
||||
build.get("etc_hosts") # type: ignore
|
||||
)
|
||||
self.nocache: bool = build.get("nocache", False)
|
||||
self.build_path: str | None = build.get("path")
|
||||
self.pull: bool | None = build.get("pull")
|
||||
self.target: str | None = build.get("target")
|
||||
self.repository: str | None = parameters["repository"]
|
||||
self.rm: bool = build.get("rm", True)
|
||||
self.state: t.Literal["absent", "present"] = parameters["state"]
|
||||
self.tag: str = parameters["tag"]
|
||||
self.http_timeout: int | None = build.get("http_timeout")
|
||||
self.pull_platform: str | None = pull.get("platform")
|
||||
self.push: bool = parameters["push"]
|
||||
self.buildargs: dict[str, t.Any] | None = build.get("args")
|
||||
self.build_platform: str | None = build.get("platform")
|
||||
self.use_config_proxy: bool | None = build.get("use_config_proxy")
|
||||
self.shm_size: int | None = convert_to_bytes(
|
||||
build.get("shm_size"), self.client.module, "build.shm_size"
|
||||
)
|
||||
self.labels = clean_dict_booleans_for_docker_api(build.get("labels"))
|
||||
self.labels: dict[str, str] = clean_dict_booleans_for_docker_api(
|
||||
build.get("labels") # type: ignore
|
||||
)
|
||||
|
||||
# If name contains a tag, it takes precedence over tag parameter.
|
||||
if not is_image_name_id(self.name):
|
||||
@@ -507,10 +524,10 @@ class ImageManager(DockerBaseClass):
|
||||
elif self.state == "absent":
|
||||
self.absent()
|
||||
|
||||
def fail(self, msg):
|
||||
def fail(self, msg: str) -> t.NoReturn:
|
||||
self.client.fail(msg)
|
||||
|
||||
def present(self):
|
||||
def present(self) -> None:
|
||||
"""
|
||||
Handles state = 'present', which includes building, loading or pulling an image,
|
||||
depending on user provided parameters.
|
||||
@@ -530,6 +547,7 @@ class ImageManager(DockerBaseClass):
|
||||
)
|
||||
|
||||
# Build the image
|
||||
assert self.build_path is not None
|
||||
if not os.path.isdir(self.build_path):
|
||||
self.fail(
|
||||
f"Requested build path {self.build_path} could not be found or you do not have access."
|
||||
@@ -546,6 +564,7 @@ class ImageManager(DockerBaseClass):
|
||||
self.results.update(self.build_image())
|
||||
|
||||
elif self.source == "load":
|
||||
assert self.load_path is not None
|
||||
# Load the image from an archive
|
||||
if not os.path.isfile(self.load_path):
|
||||
self.fail(
|
||||
@@ -596,7 +615,7 @@ class ImageManager(DockerBaseClass):
|
||||
elif self.repository:
|
||||
self.tag_image(self.name, self.tag, self.repository, push=self.push)
|
||||
|
||||
def absent(self):
|
||||
def absent(self) -> None:
|
||||
"""
|
||||
Handles state = 'absent', which removes an image.
|
||||
|
||||
@@ -627,8 +646,11 @@ class ImageManager(DockerBaseClass):
|
||||
|
||||
@staticmethod
|
||||
def archived_image_action(
|
||||
failure_logger, archive_path, current_image_name, current_image_id
|
||||
):
|
||||
failure_logger: Callable[[str], None],
|
||||
archive_path: str,
|
||||
current_image_name: str,
|
||||
current_image_id: str,
|
||||
) -> str | None:
|
||||
"""
|
||||
If the archive is missing or requires replacement, return an action message.
|
||||
|
||||
@@ -667,7 +689,7 @@ class ImageManager(DockerBaseClass):
|
||||
f"overwriting archive with image {archived.image_id} named {name}"
|
||||
)
|
||||
|
||||
def archive_image(self, name, tag):
|
||||
def archive_image(self, name: str, tag: str | None) -> None:
|
||||
"""
|
||||
Archive an image to a .tar file. Called when archive_path is passed.
|
||||
|
||||
@@ -676,6 +698,7 @@ class ImageManager(DockerBaseClass):
|
||||
:param tag: Optional image tag; assumed to be "latest" if None
|
||||
:type tag: str | None
|
||||
"""
|
||||
assert self.archive_path is not None
|
||||
|
||||
if not tag:
|
||||
tag = "latest"
|
||||
@@ -710,8 +733,8 @@ class ImageManager(DockerBaseClass):
|
||||
self.client._get(
|
||||
self.client._url("/images/{0}/get", image_name), stream=True
|
||||
),
|
||||
DEFAULT_DATA_CHUNK_SIZE,
|
||||
False,
|
||||
chunk_size=DEFAULT_DATA_CHUNK_SIZE,
|
||||
decode=False,
|
||||
)
|
||||
except Exception as exc: # pylint: disable=broad-exception-caught
|
||||
self.fail(f"Error getting image {image_name} - {exc}")
|
||||
@@ -725,7 +748,7 @@ class ImageManager(DockerBaseClass):
|
||||
|
||||
self.results["image"] = image
|
||||
|
||||
def push_image(self, name, tag=None):
|
||||
def push_image(self, name: str, tag: str | None = None) -> None:
|
||||
"""
|
||||
If the name of the image contains a repository path, then push the image.
|
||||
|
||||
@@ -799,7 +822,9 @@ class ImageManager(DockerBaseClass):
|
||||
self.results["image"] = {}
|
||||
self.results["image"]["push_status"] = status
|
||||
|
||||
def tag_image(self, name, tag, repository, push=False):
|
||||
def tag_image(
|
||||
self, name: str, tag: str, repository: str, push: bool = False
|
||||
) -> None:
|
||||
"""
|
||||
Tag an image into a repository.
|
||||
|
||||
@@ -852,7 +877,7 @@ class ImageManager(DockerBaseClass):
|
||||
self.push_image(repo, repo_tag)
|
||||
|
||||
@staticmethod
|
||||
def _extract_output_line(line, output):
|
||||
def _extract_output_line(line: dict[str, t.Any], output: list[str]):
|
||||
"""
|
||||
Extract text line from stream output and, if found, adds it to output.
|
||||
"""
|
||||
@@ -862,14 +887,15 @@ class ImageManager(DockerBaseClass):
|
||||
text_line = line.get("stream") or line.get("status") or ""
|
||||
output.extend(text_line.splitlines())
|
||||
|
||||
def build_image(self):
|
||||
def build_image(self) -> dict[str, t.Any]:
|
||||
"""
|
||||
Build an image
|
||||
|
||||
:return: image dict
|
||||
"""
|
||||
assert self.build_path is not None
|
||||
remote = context = None
|
||||
headers = {}
|
||||
headers: dict[str, str | bytes] = {}
|
||||
buildargs = {}
|
||||
if self.buildargs:
|
||||
for key, value in self.buildargs.items():
|
||||
@@ -898,12 +924,12 @@ class ImageManager(DockerBaseClass):
|
||||
[line.strip() for line in f.read().splitlines()],
|
||||
)
|
||||
)
|
||||
dockerfile = process_dockerfile(dockerfile, self.build_path)
|
||||
dockerfile_data = process_dockerfile(dockerfile, self.build_path)
|
||||
context = tar(
|
||||
self.build_path, exclude=exclude, dockerfile=dockerfile, gzip=False
|
||||
self.build_path, exclude=exclude, dockerfile=dockerfile_data, gzip=False
|
||||
)
|
||||
|
||||
params = {
|
||||
params: dict[str, t.Any] = {
|
||||
"t": f"{self.name}:{self.tag}" if self.tag else self.name,
|
||||
"remote": remote,
|
||||
"q": False,
|
||||
@@ -960,7 +986,7 @@ class ImageManager(DockerBaseClass):
|
||||
if context is not None:
|
||||
context.close()
|
||||
|
||||
build_output = []
|
||||
build_output: list[str] = []
|
||||
for line in self.client._stream_helper(response, decode=True):
|
||||
# line = json.loads(line)
|
||||
self.log(line, pretty_print=True)
|
||||
@@ -982,14 +1008,15 @@ class ImageManager(DockerBaseClass):
|
||||
"image": self.client.find_image(name=self.name, tag=self.tag),
|
||||
}
|
||||
|
||||
def load_image(self):
|
||||
def load_image(self) -> dict[str, t.Any] | None:
|
||||
"""
|
||||
Load an image from a .tar archive
|
||||
|
||||
:return: image dict
|
||||
"""
|
||||
# Load image(s) from file
|
||||
load_output = []
|
||||
assert self.load_path is not None
|
||||
load_output: list[str] = []
|
||||
has_output = False
|
||||
try:
|
||||
self.log(f"Opening image {self.load_path}")
|
||||
@@ -1078,7 +1105,7 @@ class ImageManager(DockerBaseClass):
|
||||
return self.client.find_image(self.name, self.tag)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"source": {"type": "str", "choices": ["build", "load", "pull", "local"]},
|
||||
"build": {
|
||||
|
||||
@@ -282,6 +282,7 @@ command:
|
||||
import base64
|
||||
import os
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.module_utils.common.text.formatters import human_to_bytes
|
||||
@@ -304,7 +305,16 @@ from ansible_collections.community.docker.plugins.module_utils._version import (
|
||||
)
|
||||
|
||||
|
||||
def convert_to_bytes(value, module, name, unlimited_value=None):
|
||||
if t.TYPE_CHECKING:
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def convert_to_bytes(
|
||||
value: str | None,
|
||||
module: AnsibleModule,
|
||||
name: str,
|
||||
unlimited_value: int | None = None,
|
||||
) -> int | None:
|
||||
if value is None:
|
||||
return value
|
||||
try:
|
||||
@@ -315,11 +325,11 @@ def convert_to_bytes(value, module, name, unlimited_value=None):
|
||||
module.fail_json(msg=f"Failed to convert {name} to bytes: {exc}")
|
||||
|
||||
|
||||
def dict_to_list(dictionary, concat="="):
|
||||
def dict_to_list(dictionary: dict[str, t.Any], concat: str = "=") -> list[str]:
|
||||
return [f"{k}{concat}{v}" for k, v in sorted(dictionary.items())]
|
||||
|
||||
|
||||
def _quote_csv(text):
|
||||
def _quote_csv(text: str) -> str:
|
||||
if text.strip() == text and all(i not in text for i in '",\r\n'):
|
||||
return text
|
||||
text = text.replace('"', '""')
|
||||
@@ -327,7 +337,7 @@ def _quote_csv(text):
|
||||
|
||||
|
||||
class ImageBuilder(DockerBaseClass):
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleModuleDockerClient) -> None:
|
||||
super().__init__()
|
||||
self.client = client
|
||||
self.check_mode = self.client.check_mode
|
||||
@@ -420,14 +430,14 @@ class ImageBuilder(DockerBaseClass):
|
||||
f" buildx plugin has version {buildx_version} which only supports one output."
|
||||
)
|
||||
|
||||
def fail(self, msg, **kwargs):
|
||||
def fail(self, msg: str, **kwargs: t.Any) -> t.NoReturn:
|
||||
self.client.fail(msg, **kwargs)
|
||||
|
||||
def add_list_arg(self, args, option, values):
|
||||
def add_list_arg(self, args: list[str], option: str, values: list[str]) -> None:
|
||||
for value in values:
|
||||
args.extend([option, value])
|
||||
|
||||
def add_args(self, args):
|
||||
def add_args(self, args: list[str]) -> dict[str, t.Any]:
|
||||
environ_update = {}
|
||||
if not self.outputs:
|
||||
args.extend(["--tag", f"{self.name}:{self.tag}"])
|
||||
@@ -512,9 +522,9 @@ class ImageBuilder(DockerBaseClass):
|
||||
)
|
||||
return environ_update
|
||||
|
||||
def build_image(self):
|
||||
def build_image(self) -> dict[str, t.Any]:
|
||||
image = self.client.find_image(self.name, self.tag)
|
||||
results = {
|
||||
results: dict[str, t.Any] = {
|
||||
"changed": False,
|
||||
"actions": [],
|
||||
"image": image or {},
|
||||
@@ -547,7 +557,7 @@ class ImageBuilder(DockerBaseClass):
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"name": {"type": "str", "required": True},
|
||||
"tag": {"type": "str", "default": "latest"},
|
||||
|
||||
@@ -94,6 +94,7 @@ images:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.constants import (
|
||||
DEFAULT_DATA_CHUNK_SIZE,
|
||||
@@ -121,7 +122,7 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
|
||||
|
||||
class ImageExportManager(DockerBaseClass):
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.client = client
|
||||
@@ -151,10 +152,10 @@ class ImageExportManager(DockerBaseClass):
|
||||
if not self.names:
|
||||
self.fail("At least one image name must be specified")
|
||||
|
||||
def fail(self, msg):
|
||||
def fail(self, msg: str) -> t.NoReturn:
|
||||
self.client.fail(msg)
|
||||
|
||||
def get_export_reason(self):
|
||||
def get_export_reason(self) -> str | None:
|
||||
if self.force:
|
||||
return "Exporting since force=true"
|
||||
|
||||
@@ -178,13 +179,13 @@ class ImageExportManager(DockerBaseClass):
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
return f'Overwriting archive since it contains unexpected image {archived_image.image_id} named {", ".join(archived_image.repo_tags)}'
|
||||
return f"Overwriting archive since it contains unexpected image {archived_image.image_id} named {', '.join(archived_image.repo_tags)}"
|
||||
if left_names:
|
||||
return f"Overwriting archive since it is missing image(s) {', '.join([name['joined'] for name in left_names])}"
|
||||
|
||||
return None
|
||||
|
||||
def write_chunks(self, chunks):
|
||||
def write_chunks(self, chunks: t.Generator[bytes]) -> None:
|
||||
try:
|
||||
with open(self.path, "wb") as fd:
|
||||
for chunk in chunks:
|
||||
@@ -192,7 +193,7 @@ class ImageExportManager(DockerBaseClass):
|
||||
except Exception as exc: # pylint: disable=broad-exception-caught
|
||||
self.fail(f"Error writing image archive {self.path} - {exc}")
|
||||
|
||||
def export_images(self):
|
||||
def export_images(self) -> None:
|
||||
image_names = [name["joined"] for name in self.names]
|
||||
image_names_str = ", ".join(image_names)
|
||||
if len(image_names) == 1:
|
||||
@@ -202,8 +203,8 @@ class ImageExportManager(DockerBaseClass):
|
||||
self.client._get(
|
||||
self.client._url("/images/{0}/get", image_names[0]), stream=True
|
||||
),
|
||||
DEFAULT_DATA_CHUNK_SIZE,
|
||||
False,
|
||||
chunk_size=DEFAULT_DATA_CHUNK_SIZE,
|
||||
decode=False,
|
||||
)
|
||||
except Exception as exc: # pylint: disable=broad-exception-caught
|
||||
self.fail(f"Error getting image {image_names[0]} - {exc}")
|
||||
@@ -216,15 +217,15 @@ class ImageExportManager(DockerBaseClass):
|
||||
stream=True,
|
||||
params={"names": image_names},
|
||||
),
|
||||
DEFAULT_DATA_CHUNK_SIZE,
|
||||
False,
|
||||
chunk_size=DEFAULT_DATA_CHUNK_SIZE,
|
||||
decode=False,
|
||||
)
|
||||
except Exception as exc: # pylint: disable=broad-exception-caught
|
||||
self.fail(f"Error getting images {image_names_str} - {exc}")
|
||||
|
||||
self.write_chunks(chunks)
|
||||
|
||||
def run(self):
|
||||
def run(self) -> dict[str, t.Any]:
|
||||
tag = self.tag
|
||||
if not tag:
|
||||
tag = "latest"
|
||||
@@ -260,7 +261,7 @@ class ImageExportManager(DockerBaseClass):
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"path": {"type": "path"},
|
||||
"force": {"type": "bool", "default": False},
|
||||
|
||||
@@ -136,6 +136,7 @@ images:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.errors import (
|
||||
DockerException,
|
||||
@@ -155,9 +156,7 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
|
||||
|
||||
class ImageManager(DockerBaseClass):
|
||||
|
||||
def __init__(self, client, results):
|
||||
|
||||
def __init__(self, client: AnsibleDockerClient, results: dict[str, t.Any]) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.client = client
|
||||
@@ -170,10 +169,10 @@ class ImageManager(DockerBaseClass):
|
||||
else:
|
||||
self.results["images"] = self.get_all_images()
|
||||
|
||||
def fail(self, msg):
|
||||
def fail(self, msg: str) -> t.NoReturn:
|
||||
self.client.fail(msg)
|
||||
|
||||
def get_facts(self):
|
||||
def get_facts(self) -> list[dict[str, t.Any]]:
|
||||
"""
|
||||
Lookup and inspect each image name found in the names parameter.
|
||||
|
||||
@@ -200,7 +199,7 @@ class ImageManager(DockerBaseClass):
|
||||
results.append(image)
|
||||
return results
|
||||
|
||||
def get_all_images(self):
|
||||
def get_all_images(self) -> list[dict[str, t.Any]]:
|
||||
results = []
|
||||
params = {
|
||||
"only_ids": 0,
|
||||
@@ -218,7 +217,7 @@ class ImageManager(DockerBaseClass):
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"name": {"type": "list", "elements": "str"},
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ images:
|
||||
|
||||
import errno
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.errors import (
|
||||
DockerException,
|
||||
@@ -95,7 +96,7 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
|
||||
|
||||
class ImageManager(DockerBaseClass):
|
||||
def __init__(self, client, results):
|
||||
def __init__(self, client: AnsibleDockerClient, results: dict[str, t.Any]) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.client = client
|
||||
@@ -108,7 +109,7 @@ class ImageManager(DockerBaseClass):
|
||||
self.load_images()
|
||||
|
||||
@staticmethod
|
||||
def _extract_output_line(line, output):
|
||||
def _extract_output_line(line: dict[str, t.Any], output: list[str]) -> None:
|
||||
"""
|
||||
Extract text line from stream output and, if found, adds it to output.
|
||||
"""
|
||||
@@ -118,12 +119,12 @@ class ImageManager(DockerBaseClass):
|
||||
text_line = line.get("stream") or line.get("status") or ""
|
||||
output.extend(text_line.splitlines())
|
||||
|
||||
def load_images(self):
|
||||
def load_images(self) -> None:
|
||||
"""
|
||||
Load images from a .tar archive
|
||||
"""
|
||||
# Load image(s) from file
|
||||
load_output = []
|
||||
load_output: list[str] = []
|
||||
try:
|
||||
self.log(f"Opening image {self.path}")
|
||||
with open(self.path, "rb") as image_tar:
|
||||
@@ -179,7 +180,7 @@ class ImageManager(DockerBaseClass):
|
||||
self.results["stdout"] = "\n".join(load_output)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
client = AnsibleDockerClient(
|
||||
argument_spec={
|
||||
"path": {"type": "path", "required": True},
|
||||
@@ -188,7 +189,7 @@ def main():
|
||||
)
|
||||
|
||||
try:
|
||||
results = {
|
||||
results: dict[str, t.Any] = {
|
||||
"image_names": [],
|
||||
"images": [],
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ image:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.errors import (
|
||||
DockerException,
|
||||
@@ -114,7 +115,7 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
)
|
||||
|
||||
|
||||
def image_info(image):
|
||||
def image_info(image: dict[str, t.Any] | None) -> dict[str, t.Any]:
|
||||
result = {}
|
||||
if image:
|
||||
result["id"] = image["Id"]
|
||||
@@ -124,17 +125,17 @@ def image_info(image):
|
||||
|
||||
|
||||
class ImagePuller(DockerBaseClass):
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.client = client
|
||||
self.check_mode = self.client.check_mode
|
||||
|
||||
parameters = self.client.module.params
|
||||
self.name = parameters["name"]
|
||||
self.tag = parameters["tag"]
|
||||
self.platform = parameters["platform"]
|
||||
self.pull_mode = parameters["pull"]
|
||||
self.name: str = parameters["name"]
|
||||
self.tag: str = parameters["tag"]
|
||||
self.platform: str | None = parameters["platform"]
|
||||
self.pull_mode: t.Literal["always", "not_present"] = parameters["pull"]
|
||||
|
||||
if is_image_name_id(self.name):
|
||||
self.client.fail("Cannot pull an image by ID")
|
||||
@@ -147,13 +148,15 @@ class ImagePuller(DockerBaseClass):
|
||||
self.name = repo
|
||||
self.tag = repo_tag
|
||||
|
||||
def pull(self):
|
||||
def pull(self) -> dict[str, t.Any]:
|
||||
image = self.client.find_image(name=self.name, tag=self.tag)
|
||||
actions: list[str] = []
|
||||
diff = {"before": image_info(image), "after": image_info(image)}
|
||||
results = {
|
||||
"changed": False,
|
||||
"actions": [],
|
||||
"actions": actions,
|
||||
"image": image or {},
|
||||
"diff": {"before": image_info(image), "after": image_info(image)},
|
||||
"diff": diff,
|
||||
}
|
||||
|
||||
if image and self.pull_mode == "not_present":
|
||||
@@ -175,21 +178,22 @@ class ImagePuller(DockerBaseClass):
|
||||
if compare_platform_strings(wanted_platform, image_platform):
|
||||
return results
|
||||
|
||||
results["actions"].append(f"Pulled image {self.name}:{self.tag}")
|
||||
actions.append(f"Pulled image {self.name}:{self.tag}")
|
||||
if self.check_mode:
|
||||
results["changed"] = True
|
||||
results["diff"]["after"] = image_info({"Id": "unknown"})
|
||||
diff["after"] = image_info({"Id": "unknown"})
|
||||
else:
|
||||
results["image"], not_changed = self.client.pull_image(
|
||||
image, not_changed = self.client.pull_image(
|
||||
self.name, tag=self.tag, image_platform=self.platform
|
||||
)
|
||||
results["image"] = image
|
||||
results["changed"] = not not_changed
|
||||
results["diff"]["after"] = image_info(results["image"])
|
||||
diff["after"] = image_info(image)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"name": {"type": "str", "required": True},
|
||||
"tag": {"type": "str", "default": "latest"},
|
||||
|
||||
@@ -73,6 +73,7 @@ image:
|
||||
|
||||
import base64
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.auth import (
|
||||
get_config_header,
|
||||
@@ -96,15 +97,15 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
|
||||
|
||||
class ImagePusher(DockerBaseClass):
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.client = client
|
||||
self.check_mode = self.client.check_mode
|
||||
|
||||
parameters = self.client.module.params
|
||||
self.name = parameters["name"]
|
||||
self.tag = parameters["tag"]
|
||||
self.name: str = parameters["name"]
|
||||
self.tag: str = parameters["tag"]
|
||||
|
||||
if is_image_name_id(self.name):
|
||||
self.client.fail("Cannot push an image by ID")
|
||||
@@ -122,20 +123,21 @@ class ImagePusher(DockerBaseClass):
|
||||
if not is_valid_tag(self.tag, allow_empty=False):
|
||||
self.client.fail(f'"{self.tag}" is not a valid docker tag!')
|
||||
|
||||
def push(self):
|
||||
def push(self) -> dict[str, t.Any]:
|
||||
image = self.client.find_image(name=self.name, tag=self.tag)
|
||||
if not image:
|
||||
self.client.fail(f"Cannot find image {self.name}:{self.tag}")
|
||||
|
||||
results = {
|
||||
actions: list[str] = []
|
||||
results: dict[str, t.Any] = {
|
||||
"changed": False,
|
||||
"actions": [],
|
||||
"actions": actions,
|
||||
"image": image,
|
||||
}
|
||||
|
||||
push_registry, push_repo = resolve_repository_name(self.name)
|
||||
try:
|
||||
results["actions"].append(f"Pushed image {self.name}:{self.tag}")
|
||||
actions.append(f"Pushed image {self.name}:{self.tag}")
|
||||
|
||||
headers = {}
|
||||
header = get_config_header(self.client, push_registry)
|
||||
@@ -174,7 +176,7 @@ class ImagePusher(DockerBaseClass):
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"name": {"type": "str", "required": True},
|
||||
"tag": {"type": "str", "default": "latest"},
|
||||
|
||||
@@ -98,6 +98,7 @@ untagged:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.errors import (
|
||||
DockerException,
|
||||
@@ -118,8 +119,7 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
|
||||
|
||||
class ImageRemover(DockerBaseClass):
|
||||
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.client = client
|
||||
@@ -142,10 +142,10 @@ class ImageRemover(DockerBaseClass):
|
||||
self.name = repo
|
||||
self.tag = repo_tag
|
||||
|
||||
def fail(self, msg):
|
||||
def fail(self, msg: str) -> t.NoReturn:
|
||||
self.client.fail(msg)
|
||||
|
||||
def get_diff_state(self, image):
|
||||
def get_diff_state(self, image: dict[str, t.Any] | None) -> dict[str, t.Any]:
|
||||
if not image:
|
||||
return {"exists": False}
|
||||
return {
|
||||
@@ -155,13 +155,16 @@ class ImageRemover(DockerBaseClass):
|
||||
"digests": sorted(image.get("RepoDigests") or []),
|
||||
}
|
||||
|
||||
def absent(self):
|
||||
results = {
|
||||
def absent(self) -> dict[str, t.Any]:
|
||||
actions: list[str] = []
|
||||
deleted: list[str] = []
|
||||
untagged: list[str] = []
|
||||
results: dict[str, t.Any] = {
|
||||
"changed": False,
|
||||
"actions": [],
|
||||
"actions": actions,
|
||||
"image": {},
|
||||
"deleted": [],
|
||||
"untagged": [],
|
||||
"deleted": deleted,
|
||||
"untagged": untagged,
|
||||
}
|
||||
|
||||
name = self.name
|
||||
@@ -172,16 +175,18 @@ class ImageRemover(DockerBaseClass):
|
||||
if self.tag:
|
||||
name = f"{self.name}:{self.tag}"
|
||||
|
||||
diff: dict[str, t.Any] = {}
|
||||
if self.diff:
|
||||
results["diff"] = {"before": self.get_diff_state(image)}
|
||||
results["diff"] = diff
|
||||
diff["before"] = self.get_diff_state(image)
|
||||
|
||||
if not image:
|
||||
if self.diff:
|
||||
results["diff"]["after"] = self.get_diff_state(image)
|
||||
diff["after"] = self.get_diff_state(image)
|
||||
return results
|
||||
|
||||
results["changed"] = True
|
||||
results["actions"].append(f"Removed image {name}")
|
||||
actions.append(f"Removed image {name}")
|
||||
results["image"] = image
|
||||
|
||||
if not self.check_mode:
|
||||
@@ -199,22 +204,22 @@ class ImageRemover(DockerBaseClass):
|
||||
|
||||
for entry in res:
|
||||
if entry.get("Untagged"):
|
||||
results["untagged"].append(entry["Untagged"])
|
||||
untagged.append(entry["Untagged"])
|
||||
if entry.get("Deleted"):
|
||||
results["deleted"].append(entry["Deleted"])
|
||||
deleted.append(entry["Deleted"])
|
||||
|
||||
results["untagged"] = sorted(results["untagged"])
|
||||
results["deleted"] = sorted(results["deleted"])
|
||||
untagged[:] = sorted(untagged)
|
||||
deleted[:] = sorted(deleted)
|
||||
|
||||
if self.diff:
|
||||
image_after = self.client.find_image_by_id(
|
||||
image["Id"], accept_missing_image=True
|
||||
)
|
||||
results["diff"]["after"] = self.get_diff_state(image_after)
|
||||
diff["after"] = self.get_diff_state(image_after)
|
||||
|
||||
elif is_image_name_id(name):
|
||||
results["deleted"].append(image["Id"])
|
||||
results["untagged"] = sorted(
|
||||
deleted.append(image["Id"])
|
||||
untagged[:] = sorted(
|
||||
(image.get("RepoTags") or []) + (image.get("RepoDigests") or [])
|
||||
)
|
||||
if not self.force and results["untagged"]:
|
||||
@@ -222,40 +227,40 @@ class ImageRemover(DockerBaseClass):
|
||||
"Cannot delete image by ID that is still in use - use force=true"
|
||||
)
|
||||
if self.diff:
|
||||
results["diff"]["after"] = self.get_diff_state({})
|
||||
diff["after"] = self.get_diff_state({})
|
||||
|
||||
elif is_image_name_id(self.tag):
|
||||
results["untagged"].append(name)
|
||||
untagged.append(name)
|
||||
if (
|
||||
len(image.get("RepoTags") or []) < 1
|
||||
and len(image.get("RepoDigests") or []) < 2
|
||||
):
|
||||
results["deleted"].append(image["Id"])
|
||||
deleted.append(image["Id"])
|
||||
if self.diff:
|
||||
results["diff"]["after"] = self.get_diff_state(image)
|
||||
diff["after"] = self.get_diff_state(image)
|
||||
try:
|
||||
results["diff"]["after"]["digests"].remove(name)
|
||||
diff["after"]["digests"].remove(name)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
else:
|
||||
results["untagged"].append(name)
|
||||
untagged.append(name)
|
||||
if (
|
||||
len(image.get("RepoTags") or []) < 2
|
||||
and len(image.get("RepoDigests") or []) < 1
|
||||
):
|
||||
results["deleted"].append(image["Id"])
|
||||
deleted.append(image["Id"])
|
||||
if self.diff:
|
||||
results["diff"]["after"] = self.get_diff_state(image)
|
||||
diff["after"] = self.get_diff_state(image)
|
||||
try:
|
||||
results["diff"]["after"]["tags"].remove(name)
|
||||
diff["after"]["tags"].remove(name)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"name": {"type": "str", "required": True},
|
||||
"tag": {"type": "str", "default": "latest"},
|
||||
|
||||
@@ -101,6 +101,7 @@ tagged_images:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.text.formatters import human_to_bytes
|
||||
|
||||
@@ -121,7 +122,16 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
)
|
||||
|
||||
|
||||
def convert_to_bytes(value, module, name, unlimited_value=None):
|
||||
if t.TYPE_CHECKING:
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def convert_to_bytes(
|
||||
value: str | None,
|
||||
module: AnsibleModule,
|
||||
name: str,
|
||||
unlimited_value: int | None = None,
|
||||
) -> int | None:
|
||||
if value is None:
|
||||
return value
|
||||
try:
|
||||
@@ -132,8 +142,8 @@ def convert_to_bytes(value, module, name, unlimited_value=None):
|
||||
module.fail_json(msg=f"Failed to convert {name} to bytes: {exc}")
|
||||
|
||||
|
||||
def image_info(name, tag, image):
|
||||
result = {"name": name, "tag": tag}
|
||||
def image_info(name: str, tag: str, image: dict[str, t.Any] | None) -> dict[str, t.Any]:
|
||||
result: dict[str, t.Any] = {"name": name, "tag": tag}
|
||||
if image:
|
||||
result["id"] = image["Id"]
|
||||
else:
|
||||
@@ -142,7 +152,7 @@ def image_info(name, tag, image):
|
||||
|
||||
|
||||
class ImageTagger(DockerBaseClass):
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.client = client
|
||||
@@ -179,10 +189,12 @@ class ImageTagger(DockerBaseClass):
|
||||
)
|
||||
self.repositories.append((repo, repo_tag))
|
||||
|
||||
def fail(self, msg):
|
||||
def fail(self, msg: str) -> t.NoReturn:
|
||||
self.client.fail(msg)
|
||||
|
||||
def tag_image(self, image, name, tag):
|
||||
def tag_image(
|
||||
self, image: dict[str, t.Any], name: str, tag: str
|
||||
) -> tuple[bool, str, dict[str, t.Any] | None]:
|
||||
tagged_image = self.client.find_image(name=name, tag=tag)
|
||||
if tagged_image:
|
||||
# Idempotency checks
|
||||
@@ -220,20 +232,22 @@ class ImageTagger(DockerBaseClass):
|
||||
|
||||
return True, msg, tagged_image
|
||||
|
||||
def tag_images(self):
|
||||
def tag_images(self) -> dict[str, t.Any]:
|
||||
if is_image_name_id(self.name):
|
||||
image = self.client.find_image_by_id(self.name, accept_missing_image=False)
|
||||
else:
|
||||
image = self.client.find_image(name=self.name, tag=self.tag)
|
||||
if not image:
|
||||
self.fail(f"Cannot find image {self.name}:{self.tag}")
|
||||
assert image is not None
|
||||
|
||||
before = []
|
||||
after = []
|
||||
tagged_images = []
|
||||
results = {
|
||||
before: list[dict[str, t.Any]] = []
|
||||
after: list[dict[str, t.Any]] = []
|
||||
tagged_images: list[str] = []
|
||||
actions: list[str] = []
|
||||
results: dict[str, t.Any] = {
|
||||
"changed": False,
|
||||
"actions": [],
|
||||
"actions": actions,
|
||||
"image": image,
|
||||
"tagged_images": tagged_images,
|
||||
"diff": {"before": {"images": before}, "after": {"images": after}},
|
||||
@@ -244,19 +258,19 @@ class ImageTagger(DockerBaseClass):
|
||||
after.append(image_info(repository, tag, image if tagged else old_image))
|
||||
if tagged:
|
||||
results["changed"] = True
|
||||
results["actions"].append(
|
||||
actions.append(
|
||||
f"Tagged image {image['Id']} as {repository}:{tag}: {msg}"
|
||||
)
|
||||
tagged_images.append(f"{repository}:{tag}")
|
||||
else:
|
||||
results["actions"].append(
|
||||
actions.append(
|
||||
f"Not tagged image {image['Id']} as {repository}:{tag}: {msg}"
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"name": {"type": "str", "required": True},
|
||||
"tag": {"type": "str", "default": "latest"},
|
||||
|
||||
@@ -120,6 +120,7 @@ import base64
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||
|
||||
@@ -154,11 +155,11 @@ class DockerFileStore:
|
||||
|
||||
program = "<legacy config>"
|
||||
|
||||
def __init__(self, config_path):
|
||||
def __init__(self, config_path: str) -> None:
|
||||
self._config_path = config_path
|
||||
|
||||
# Make sure we have a minimal config if none is available.
|
||||
self._config = {"auths": {}}
|
||||
self._config: dict[str, t.Any] = {"auths": {}}
|
||||
|
||||
try:
|
||||
# Attempt to read the existing config.
|
||||
@@ -172,14 +173,14 @@ class DockerFileStore:
|
||||
self._config.update(config)
|
||||
|
||||
@property
|
||||
def config_path(self):
|
||||
def config_path(self) -> str:
|
||||
"""
|
||||
Return the config path configured in this DockerFileStore instance.
|
||||
"""
|
||||
|
||||
return self._config_path
|
||||
|
||||
def get(self, server):
|
||||
def get(self, server: str) -> dict[str, t.Any]:
|
||||
"""
|
||||
Retrieve credentials for `server` if there are any in the config file.
|
||||
Otherwise raise a `StoreError`
|
||||
@@ -193,7 +194,7 @@ class DockerFileStore:
|
||||
|
||||
return {"Username": username, "Secret": password}
|
||||
|
||||
def _write(self):
|
||||
def _write(self) -> None:
|
||||
"""
|
||||
Write config back out to disk.
|
||||
"""
|
||||
@@ -209,7 +210,7 @@ class DockerFileStore:
|
||||
finally:
|
||||
os.close(f)
|
||||
|
||||
def store(self, server, username, password):
|
||||
def store(self, server: str, username: str, password: str) -> None:
|
||||
"""
|
||||
Add a credentials for `server` to the current configuration.
|
||||
"""
|
||||
@@ -225,7 +226,7 @@ class DockerFileStore:
|
||||
|
||||
self._write()
|
||||
|
||||
def erase(self, server):
|
||||
def erase(self, server: str) -> None:
|
||||
"""
|
||||
Remove credentials for the given server from the configuration.
|
||||
"""
|
||||
@@ -236,9 +237,7 @@ class DockerFileStore:
|
||||
|
||||
|
||||
class LoginManager(DockerBaseClass):
|
||||
|
||||
def __init__(self, client, results):
|
||||
|
||||
def __init__(self, client: AnsibleDockerClient, results: dict[str, t.Any]) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.client = client
|
||||
@@ -246,14 +245,14 @@ class LoginManager(DockerBaseClass):
|
||||
parameters = self.client.module.params
|
||||
self.check_mode = self.client.check_mode
|
||||
|
||||
self.registry_url = parameters.get("registry_url")
|
||||
self.username = parameters.get("username")
|
||||
self.password = parameters.get("password")
|
||||
self.reauthorize = parameters.get("reauthorize")
|
||||
self.config_path = parameters.get("config_path")
|
||||
self.state = parameters.get("state")
|
||||
self.registry_url: str = parameters.get("registry_url")
|
||||
self.username: str | None = parameters.get("username")
|
||||
self.password: str | None = parameters.get("password")
|
||||
self.reauthorize: bool = parameters.get("reauthorize")
|
||||
self.config_path: str = parameters.get("config_path")
|
||||
self.state: t.Literal["present", "absent"] = parameters.get("state")
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
"""
|
||||
Do the actual work of this task here. This allows instantiation for partial
|
||||
testing.
|
||||
@@ -264,10 +263,10 @@ class LoginManager(DockerBaseClass):
|
||||
else:
|
||||
self.logout()
|
||||
|
||||
def fail(self, msg):
|
||||
def fail(self, msg: str) -> t.NoReturn:
|
||||
self.client.fail(msg)
|
||||
|
||||
def _login(self, reauth):
|
||||
def _login(self, reauth: bool) -> dict[str, t.Any]:
|
||||
if self.config_path and os.path.exists(self.config_path):
|
||||
self.client._auth_configs = auth.load_config(
|
||||
self.config_path, credstore_env=self.client.credstore_env
|
||||
@@ -297,7 +296,7 @@ class LoginManager(DockerBaseClass):
|
||||
)
|
||||
return self.client._result(response, get_json=True)
|
||||
|
||||
def login(self):
|
||||
def login(self) -> None:
|
||||
"""
|
||||
Log into the registry with provided username/password. On success update the config
|
||||
file with the new authorization.
|
||||
@@ -331,7 +330,7 @@ class LoginManager(DockerBaseClass):
|
||||
|
||||
self.update_credentials()
|
||||
|
||||
def logout(self):
|
||||
def logout(self) -> None:
|
||||
"""
|
||||
Log out of the registry. On success update the config file.
|
||||
|
||||
@@ -353,13 +352,16 @@ class LoginManager(DockerBaseClass):
|
||||
store.erase(self.registry_url)
|
||||
self.results["changed"] = True
|
||||
|
||||
def update_credentials(self):
|
||||
def update_credentials(self) -> None:
|
||||
"""
|
||||
If the authorization is not stored attempt to store authorization values via
|
||||
the appropriate credential helper or to the config file.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
# This is only called from login()
|
||||
assert self.username is not None
|
||||
assert self.password is not None
|
||||
|
||||
# Check to see if credentials already exist.
|
||||
store = self.get_credential_store_instance(self.registry_url, self.config_path)
|
||||
@@ -385,7 +387,9 @@ class LoginManager(DockerBaseClass):
|
||||
)
|
||||
self.results["changed"] = True
|
||||
|
||||
def get_credential_store_instance(self, registry, dockercfg_path):
|
||||
def get_credential_store_instance(
|
||||
self, registry: str, dockercfg_path: str
|
||||
) -> Store | DockerFileStore:
|
||||
"""
|
||||
Return an instance of docker.credentials.Store used by the given registry.
|
||||
|
||||
@@ -408,8 +412,7 @@ class LoginManager(DockerBaseClass):
|
||||
return DockerFileStore(dockercfg_path)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"registry_url": {
|
||||
"type": "str",
|
||||
|
||||
@@ -284,6 +284,7 @@ network:
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
@@ -303,29 +304,31 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
|
||||
|
||||
class TaskParameters(DockerBaseClass):
|
||||
def __init__(self, client):
|
||||
name: str
|
||||
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
super().__init__()
|
||||
self.client = client
|
||||
|
||||
self.name = None
|
||||
self.connected = None
|
||||
self.config_from = None
|
||||
self.config_only = None
|
||||
self.driver = None
|
||||
self.driver_options = None
|
||||
self.ipam_driver = None
|
||||
self.ipam_driver_options = None
|
||||
self.ipam_config = None
|
||||
self.appends = None
|
||||
self.force = None
|
||||
self.internal = None
|
||||
self.labels = None
|
||||
self.debug = None
|
||||
self.enable_ipv4 = None
|
||||
self.enable_ipv6 = None
|
||||
self.scope = None
|
||||
self.attachable = None
|
||||
self.ingress = None
|
||||
self.connected: list[str] = []
|
||||
self.config_from: str | None = None
|
||||
self.config_only: bool | None = None
|
||||
self.driver: str = "bridge"
|
||||
self.driver_options: dict[str, t.Any] = {}
|
||||
self.ipam_driver: str | None = None
|
||||
self.ipam_driver_options: dict[str, t.Any] | None = None
|
||||
self.ipam_config: list[dict[str, t.Any]] | None = None
|
||||
self.appends: bool = False
|
||||
self.force: bool = False
|
||||
self.internal: bool | None = None
|
||||
self.labels: dict[str, t.Any] = {}
|
||||
self.debug: bool = False
|
||||
self.enable_ipv4: bool | None = None
|
||||
self.enable_ipv6: bool | None = None
|
||||
self.scope: t.Literal["local", "global", "swarm"] | None = None
|
||||
self.attachable: bool | None = None
|
||||
self.ingress: bool | None = None
|
||||
self.state: t.Literal["present", "absent"] = "present"
|
||||
|
||||
for key, value in client.module.params.items():
|
||||
setattr(self, key, value)
|
||||
@@ -333,10 +336,10 @@ class TaskParameters(DockerBaseClass):
|
||||
# config_only sets driver to 'null' (and scope to 'local') so force that here. Otherwise we get
|
||||
# diffs of 'null' --> 'bridge' given that the driver option defaults to 'bridge'.
|
||||
if self.config_only:
|
||||
self.driver = "null"
|
||||
self.driver = "null" # type: ignore[unreachable]
|
||||
|
||||
|
||||
def container_names_in_network(network):
|
||||
def container_names_in_network(network: dict[str, t.Any]) -> list[str]:
|
||||
return (
|
||||
[c["Name"] for c in network["Containers"].values()]
|
||||
if network["Containers"]
|
||||
@@ -348,7 +351,7 @@ CIDR_IPV4 = re.compile(r"^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$
|
||||
CIDR_IPV6 = re.compile(r"^[0-9a-fA-F:]+/([0-9]|[1-9][0-9]|1[0-2][0-9])$")
|
||||
|
||||
|
||||
def validate_cidr(cidr):
|
||||
def validate_cidr(cidr: str) -> t.Literal["ipv4", "ipv6"]:
|
||||
"""Validate CIDR. Return IP version of a CIDR string on success.
|
||||
|
||||
:param cidr: Valid CIDR
|
||||
@@ -364,7 +367,7 @@ def validate_cidr(cidr):
|
||||
raise ValueError(f'"{cidr}" is not a valid CIDR')
|
||||
|
||||
|
||||
def normalize_ipam_config_key(key):
|
||||
def normalize_ipam_config_key(key: str) -> str:
|
||||
"""Normalizes IPAM config keys returned by Docker API to match Ansible keys.
|
||||
|
||||
:param key: Docker API key
|
||||
@@ -376,7 +379,7 @@ def normalize_ipam_config_key(key):
|
||||
return special_cases.get(key, key.lower())
|
||||
|
||||
|
||||
def dicts_are_essentially_equal(a, b):
|
||||
def dicts_are_essentially_equal(a: dict[str, t.Any], b: dict[str, t.Any]):
|
||||
"""Make sure that a is a subset of b, where None entries of a are ignored."""
|
||||
for k, v in a.items():
|
||||
if v is None:
|
||||
@@ -387,15 +390,15 @@ def dicts_are_essentially_equal(a, b):
|
||||
|
||||
|
||||
class DockerNetworkManager:
|
||||
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
self.client = client
|
||||
self.parameters = TaskParameters(client)
|
||||
self.check_mode = self.client.check_mode
|
||||
self.results = {"changed": False, "actions": []}
|
||||
self.actions: list[str] = []
|
||||
self.results: dict[str, t.Any] = {"changed": False, "actions": self.actions}
|
||||
self.diff = self.client.module._diff
|
||||
self.diff_tracker = DifferenceTracker()
|
||||
self.diff_result = {}
|
||||
self.diff_result: dict[str, t.Any] = {}
|
||||
|
||||
self.existing_network = self.get_existing_network()
|
||||
|
||||
@@ -429,10 +432,12 @@ class DockerNetworkManager:
|
||||
)
|
||||
self.results["diff"] = self.diff_result
|
||||
|
||||
def get_existing_network(self):
|
||||
def get_existing_network(self) -> dict[str, t.Any] | None:
|
||||
return self.client.get_network(name=self.parameters.name)
|
||||
|
||||
def has_different_config(self, net):
|
||||
def has_different_config(
|
||||
self, net: dict[str, t.Any]
|
||||
) -> tuple[bool, DifferenceTracker]:
|
||||
"""
|
||||
Evaluates an existing network and returns a tuple containing a boolean
|
||||
indicating if the configuration is different and a list of differences.
|
||||
@@ -601,9 +606,9 @@ class DockerNetworkManager:
|
||||
|
||||
return not differences.empty, differences
|
||||
|
||||
def create_network(self):
|
||||
def create_network(self) -> None:
|
||||
if not self.existing_network:
|
||||
data = {
|
||||
data: dict[str, t.Any] = {
|
||||
"Name": self.parameters.name,
|
||||
"Driver": self.parameters.driver,
|
||||
"Options": self.parameters.driver_options,
|
||||
@@ -661,12 +666,12 @@ class DockerNetworkManager:
|
||||
resp = self.client.post_json_to_json("/networks/create", data=data)
|
||||
self.client.report_warnings(resp, ["Warning"])
|
||||
self.existing_network = self.client.get_network(network_id=resp["Id"])
|
||||
self.results["actions"].append(
|
||||
self.actions.append(
|
||||
f"Created network {self.parameters.name} with driver {self.parameters.driver}"
|
||||
)
|
||||
self.results["changed"] = True
|
||||
|
||||
def remove_network(self):
|
||||
def remove_network(self) -> None:
|
||||
if self.existing_network:
|
||||
self.disconnect_all_containers()
|
||||
if not self.check_mode:
|
||||
@@ -674,15 +679,15 @@ class DockerNetworkManager:
|
||||
if self.existing_network.get("Scope", "local") == "swarm":
|
||||
while self.get_existing_network():
|
||||
time.sleep(0.1)
|
||||
self.results["actions"].append(f"Removed network {self.parameters.name}")
|
||||
self.actions.append(f"Removed network {self.parameters.name}")
|
||||
self.results["changed"] = True
|
||||
|
||||
def is_container_connected(self, container_name):
|
||||
def is_container_connected(self, container_name: str) -> bool:
|
||||
if not self.existing_network:
|
||||
return False
|
||||
return container_name in container_names_in_network(self.existing_network)
|
||||
|
||||
def is_container_exist(self, container_name):
|
||||
def is_container_exist(self, container_name: str) -> bool:
|
||||
try:
|
||||
container = self.client.get_container(container_name)
|
||||
return bool(container)
|
||||
@@ -698,7 +703,7 @@ class DockerNetworkManager:
|
||||
exception=traceback.format_exc(),
|
||||
)
|
||||
|
||||
def connect_containers(self):
|
||||
def connect_containers(self) -> None:
|
||||
for name in self.parameters.connected:
|
||||
if not self.is_container_connected(name) and self.is_container_exist(name):
|
||||
if not self.check_mode:
|
||||
@@ -709,11 +714,11 @@ class DockerNetworkManager:
|
||||
self.client.post_json(
|
||||
"/networks/{0}/connect", self.parameters.name, data=data
|
||||
)
|
||||
self.results["actions"].append(f"Connected container {name}")
|
||||
self.actions.append(f"Connected container {name}")
|
||||
self.results["changed"] = True
|
||||
self.diff_tracker.add(f"connected.{name}", parameter=True, active=False)
|
||||
|
||||
def disconnect_missing(self):
|
||||
def disconnect_missing(self) -> None:
|
||||
if not self.existing_network:
|
||||
return
|
||||
containers = self.existing_network["Containers"]
|
||||
@@ -724,26 +729,29 @@ class DockerNetworkManager:
|
||||
if name not in self.parameters.connected:
|
||||
self.disconnect_container(name)
|
||||
|
||||
def disconnect_all_containers(self):
|
||||
containers = self.client.get_network(name=self.parameters.name)["Containers"]
|
||||
def disconnect_all_containers(self) -> None:
|
||||
network = self.client.get_network(name=self.parameters.name)
|
||||
if not network:
|
||||
return
|
||||
containers = network["Containers"]
|
||||
if not containers:
|
||||
return
|
||||
for cont in containers.values():
|
||||
self.disconnect_container(cont["Name"])
|
||||
|
||||
def disconnect_container(self, container_name):
|
||||
def disconnect_container(self, container_name: str) -> None:
|
||||
if not self.check_mode:
|
||||
data = {"Container": container_name, "Force": True}
|
||||
self.client.post_json(
|
||||
"/networks/{0}/disconnect", self.parameters.name, data=data
|
||||
)
|
||||
self.results["actions"].append(f"Disconnected container {container_name}")
|
||||
self.actions.append(f"Disconnected container {container_name}")
|
||||
self.results["changed"] = True
|
||||
self.diff_tracker.add(
|
||||
f"connected.{container_name}", parameter=False, active=True
|
||||
)
|
||||
|
||||
def present(self):
|
||||
def present(self) -> None:
|
||||
different = False
|
||||
differences = DifferenceTracker()
|
||||
if self.existing_network:
|
||||
@@ -771,14 +779,14 @@ class DockerNetworkManager:
|
||||
network_facts = self.get_existing_network()
|
||||
self.results["network"] = network_facts
|
||||
|
||||
def absent(self):
|
||||
def absent(self) -> None:
|
||||
self.diff_tracker.add(
|
||||
"exists", parameter=False, active=self.existing_network is not None
|
||||
)
|
||||
self.remove_network()
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"name": {"type": "str", "required": True, "aliases": ["network_name"]},
|
||||
"config_from": {"type": "str"},
|
||||
|
||||
@@ -107,7 +107,7 @@ from ansible_collections.community.docker.plugins.module_utils._common_api impor
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"name": {"type": "str", "required": True},
|
||||
}
|
||||
|
||||
@@ -129,6 +129,7 @@ actions:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
@@ -149,35 +150,36 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
|
||||
|
||||
class TaskParameters(DockerBaseClass):
|
||||
def __init__(self, client):
|
||||
plugin_name: str
|
||||
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
super().__init__()
|
||||
self.client = client
|
||||
self.plugin_name = None
|
||||
self.alias = None
|
||||
self.plugin_options = None
|
||||
self.debug = None
|
||||
self.force_remove = None
|
||||
self.enable_timeout = None
|
||||
self.alias: str | None = None
|
||||
self.plugin_options: dict[str, t.Any] = {}
|
||||
self.debug: bool = False
|
||||
self.force_remove: bool = False
|
||||
self.enable_timeout: int = 0
|
||||
self.state: t.Literal["present", "absent", "enable", "disable"] = "present"
|
||||
|
||||
for key, value in client.module.params.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
def prepare_options(options):
|
||||
def prepare_options(options: dict[str, t.Any] | None) -> list[str]:
|
||||
return (
|
||||
[f'{k}={v if v is not None else ""}' for k, v in options.items()]
|
||||
[f"{k}={v if v is not None else ''}" for k, v in options.items()]
|
||||
if options
|
||||
else []
|
||||
)
|
||||
|
||||
|
||||
def parse_options(options_list):
|
||||
def parse_options(options_list: list[str] | None) -> dict[str, str]:
|
||||
return dict(x.split("=", 1) for x in options_list) if options_list else {}
|
||||
|
||||
|
||||
class DockerPluginManager:
|
||||
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
self.client = client
|
||||
|
||||
self.parameters = TaskParameters(client)
|
||||
@@ -185,9 +187,9 @@ class DockerPluginManager:
|
||||
self.check_mode = self.client.check_mode
|
||||
self.diff = self.client.module._diff
|
||||
self.diff_tracker = DifferenceTracker()
|
||||
self.diff_result = {}
|
||||
self.diff_result: dict[str, t.Any] = {}
|
||||
|
||||
self.actions = []
|
||||
self.actions: list[str] = []
|
||||
self.changed = False
|
||||
|
||||
self.existing_plugin = self.get_existing_plugin()
|
||||
@@ -209,7 +211,7 @@ class DockerPluginManager:
|
||||
)
|
||||
self.diff = self.diff_result
|
||||
|
||||
def get_existing_plugin(self):
|
||||
def get_existing_plugin(self) -> dict[str, t.Any] | None:
|
||||
try:
|
||||
return self.client.get_json("/plugins/{0}/json", self.preferred_name)
|
||||
except NotFound:
|
||||
@@ -217,12 +219,13 @@ class DockerPluginManager:
|
||||
except APIError as e:
|
||||
self.client.fail(to_native(e))
|
||||
|
||||
def has_different_config(self):
|
||||
def has_different_config(self) -> DifferenceTracker:
|
||||
"""
|
||||
Return the list of differences between the current parameters and the existing plugin.
|
||||
|
||||
:return: list of options that differ
|
||||
"""
|
||||
assert self.existing_plugin is not None
|
||||
differences = DifferenceTracker()
|
||||
if self.parameters.plugin_options:
|
||||
settings = self.existing_plugin.get("Settings")
|
||||
@@ -249,7 +252,7 @@ class DockerPluginManager:
|
||||
|
||||
return differences
|
||||
|
||||
def install_plugin(self):
|
||||
def install_plugin(self) -> None:
|
||||
if not self.existing_plugin:
|
||||
if not self.check_mode:
|
||||
try:
|
||||
@@ -297,7 +300,7 @@ class DockerPluginManager:
|
||||
self.actions.append(f"Installed plugin {self.preferred_name}")
|
||||
self.changed = True
|
||||
|
||||
def remove_plugin(self):
|
||||
def remove_plugin(self) -> None:
|
||||
force = self.parameters.force_remove
|
||||
if self.existing_plugin:
|
||||
if not self.check_mode:
|
||||
@@ -311,7 +314,7 @@ class DockerPluginManager:
|
||||
self.actions.append(f"Removed plugin {self.preferred_name}")
|
||||
self.changed = True
|
||||
|
||||
def update_plugin(self):
|
||||
def update_plugin(self) -> None:
|
||||
if self.existing_plugin:
|
||||
differences = self.has_different_config()
|
||||
if not differences.empty:
|
||||
@@ -328,7 +331,7 @@ class DockerPluginManager:
|
||||
else:
|
||||
self.client.fail("Cannot update the plugin: Plugin does not exist")
|
||||
|
||||
def present(self):
|
||||
def present(self) -> None:
|
||||
differences = DifferenceTracker()
|
||||
if self.existing_plugin:
|
||||
differences = self.has_different_config()
|
||||
@@ -345,13 +348,10 @@ class DockerPluginManager:
|
||||
if self.diff or self.check_mode or self.parameters.debug:
|
||||
self.diff_tracker.merge(differences)
|
||||
|
||||
if not self.check_mode and not self.parameters.debug:
|
||||
self.actions = None
|
||||
|
||||
def absent(self):
|
||||
def absent(self) -> None:
|
||||
self.remove_plugin()
|
||||
|
||||
def enable(self):
|
||||
def enable(self) -> None:
|
||||
timeout = self.parameters.enable_timeout
|
||||
if self.existing_plugin:
|
||||
if not self.existing_plugin.get("Enabled"):
|
||||
@@ -380,7 +380,7 @@ class DockerPluginManager:
|
||||
self.actions.append(f"Enabled plugin {self.preferred_name}")
|
||||
self.changed = True
|
||||
|
||||
def disable(self):
|
||||
def disable(self) -> None:
|
||||
if self.existing_plugin:
|
||||
if self.existing_plugin.get("Enabled"):
|
||||
if not self.check_mode:
|
||||
@@ -396,7 +396,7 @@ class DockerPluginManager:
|
||||
self.client.fail("Plugin not found: Plugin does not exist.")
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
def result(self) -> dict[str, t.Any]:
|
||||
plugin_data = {}
|
||||
if self.parameters.state != "absent":
|
||||
try:
|
||||
@@ -406,16 +406,22 @@ class DockerPluginManager:
|
||||
except NotFound:
|
||||
# This can happen in check mode
|
||||
pass
|
||||
result = {
|
||||
result: dict[str, t.Any] = {
|
||||
"actions": self.actions,
|
||||
"changed": self.changed,
|
||||
"diff": self.diff,
|
||||
"plugin": plugin_data,
|
||||
}
|
||||
return dict((k, v) for k, v in result.items() if v is not None)
|
||||
if (
|
||||
self.parameters.state == "present"
|
||||
and not self.check_mode
|
||||
and not self.parameters.debug
|
||||
):
|
||||
result["actions"] = None
|
||||
return {k: v for k, v in result.items() if v is not None}
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"alias": {"type": "str"},
|
||||
"plugin_name": {"type": "str", "required": True},
|
||||
|
||||
@@ -247,7 +247,7 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"containers": {"type": "bool", "default": False},
|
||||
"containers_filters": {"type": "dict"},
|
||||
|
||||
@@ -118,6 +118,7 @@ volume:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
@@ -137,31 +138,33 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
|
||||
|
||||
class TaskParameters(DockerBaseClass):
|
||||
def __init__(self, client):
|
||||
volume_name: str
|
||||
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
super().__init__()
|
||||
self.client = client
|
||||
|
||||
self.volume_name = None
|
||||
self.driver = None
|
||||
self.driver_options = None
|
||||
self.labels = None
|
||||
self.recreate = None
|
||||
self.debug = None
|
||||
self.driver: str = "local"
|
||||
self.driver_options: dict[str, t.Any] = {}
|
||||
self.labels: dict[str, t.Any] | None = None
|
||||
self.recreate: t.Literal["always", "never", "options-changed"] = "never"
|
||||
self.debug: bool = False
|
||||
self.state: t.Literal["present", "absent"] = "present"
|
||||
|
||||
for key, value in client.module.params.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
class DockerVolumeManager:
|
||||
|
||||
def __init__(self, client):
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
self.client = client
|
||||
self.parameters = TaskParameters(client)
|
||||
self.check_mode = self.client.check_mode
|
||||
self.results = {"changed": False, "actions": []}
|
||||
self.actions: list[str] = []
|
||||
self.results: dict[str, t.Any] = {"changed": False, "actions": self.actions}
|
||||
self.diff = self.client.module._diff
|
||||
self.diff_tracker = DifferenceTracker()
|
||||
self.diff_result = {}
|
||||
self.diff_result: dict[str, t.Any] = {}
|
||||
|
||||
self.existing_volume = self.get_existing_volume()
|
||||
|
||||
@@ -178,7 +181,7 @@ class DockerVolumeManager:
|
||||
)
|
||||
self.results["diff"] = self.diff_result
|
||||
|
||||
def get_existing_volume(self):
|
||||
def get_existing_volume(self) -> dict[str, t.Any] | None:
|
||||
try:
|
||||
volumes = self.client.get_json("/volumes")
|
||||
except APIError as e:
|
||||
@@ -193,12 +196,13 @@ class DockerVolumeManager:
|
||||
|
||||
return None
|
||||
|
||||
def has_different_config(self):
|
||||
def has_different_config(self) -> DifferenceTracker:
|
||||
"""
|
||||
Return the list of differences between the current parameters and the existing volume.
|
||||
|
||||
:return: list of options that differ
|
||||
"""
|
||||
assert self.existing_volume is not None
|
||||
differences = DifferenceTracker()
|
||||
if (
|
||||
self.parameters.driver
|
||||
@@ -239,7 +243,7 @@ class DockerVolumeManager:
|
||||
|
||||
return differences
|
||||
|
||||
def create_volume(self):
|
||||
def create_volume(self) -> None:
|
||||
if not self.existing_volume:
|
||||
if not self.check_mode:
|
||||
try:
|
||||
@@ -257,12 +261,12 @@ class DockerVolumeManager:
|
||||
except APIError as e:
|
||||
self.client.fail(to_native(e))
|
||||
|
||||
self.results["actions"].append(
|
||||
self.actions.append(
|
||||
f"Created volume {self.parameters.volume_name} with driver {self.parameters.driver}"
|
||||
)
|
||||
self.results["changed"] = True
|
||||
|
||||
def remove_volume(self):
|
||||
def remove_volume(self) -> None:
|
||||
if self.existing_volume:
|
||||
if not self.check_mode:
|
||||
try:
|
||||
@@ -270,12 +274,10 @@ class DockerVolumeManager:
|
||||
except APIError as e:
|
||||
self.client.fail(to_native(e))
|
||||
|
||||
self.results["actions"].append(
|
||||
f"Removed volume {self.parameters.volume_name}"
|
||||
)
|
||||
self.actions.append(f"Removed volume {self.parameters.volume_name}")
|
||||
self.results["changed"] = True
|
||||
|
||||
def present(self):
|
||||
def present(self) -> None:
|
||||
differences = DifferenceTracker()
|
||||
if self.existing_volume:
|
||||
differences = self.has_different_config()
|
||||
@@ -301,14 +303,14 @@ class DockerVolumeManager:
|
||||
volume_facts = self.get_existing_volume()
|
||||
self.results["volume"] = volume_facts
|
||||
|
||||
def absent(self):
|
||||
def absent(self) -> None:
|
||||
self.diff_tracker.add(
|
||||
"exists", parameter=False, active=self.existing_volume is not None
|
||||
)
|
||||
self.remove_volume()
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"volume_name": {"type": "str", "required": True, "aliases": ["name"]},
|
||||
"state": {
|
||||
|
||||
@@ -71,6 +71,7 @@ volume:
|
||||
"""
|
||||
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.errors import (
|
||||
DockerException,
|
||||
@@ -82,7 +83,9 @@ from ansible_collections.community.docker.plugins.module_utils._common_api impor
|
||||
)
|
||||
|
||||
|
||||
def get_existing_volume(client, volume_name):
|
||||
def get_existing_volume(
|
||||
client: AnsibleDockerClient, volume_name: str
|
||||
) -> dict[str, t.Any] | None:
|
||||
try:
|
||||
return client.get_json("/volumes/{0}", volume_name)
|
||||
except NotFound:
|
||||
@@ -91,7 +94,7 @@ def get_existing_volume(client, volume_name):
|
||||
client.fail(f"Error inspecting volume: {exc}")
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
argument_spec = {
|
||||
"name": {"type": "str", "required": True, "aliases": ["volume_name"]},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user