update ansible collections sops, docker and debops
This commit is contained in:
parent
f1135b818f
commit
e31ee5e39c
910 changed files with 27667 additions and 19786 deletions
|
|
@ -19,6 +19,7 @@ notes:
|
|||
with Python's C(SSLSocket)s. See U(https://github.com/ansible-collections/community.docker/issues/605) for more information.
|
||||
extends_documentation_fragment:
|
||||
- community.docker._docker.api_documentation
|
||||
- community.docker._docker.api_environment_documentation
|
||||
- community.docker._docker.var_names
|
||||
options:
|
||||
remote_user:
|
||||
|
|
@ -303,7 +304,7 @@ class Connection(ConnectionBase):
|
|||
|
||||
data = {"Tty": False, "Detach": False}
|
||||
if need_stdin:
|
||||
exec_socket = self._call_client(
|
||||
exec_socket, response = self._call_client(
|
||||
lambda client: client.post_json_to_stream_socket(
|
||||
"/exec/{0}/start", exec_id, data=data
|
||||
)
|
||||
|
|
@ -356,7 +357,7 @@ class Connection(ConnectionBase):
|
|||
|
||||
stdout, stderr = exec_socket_handler.consume()
|
||||
finally:
|
||||
exec_socket.close()
|
||||
response.close()
|
||||
else:
|
||||
stdout, stderr = self._call_client(
|
||||
lambda client: client.post_json_to_stream(
|
||||
|
|
|
|||
|
|
@ -387,3 +387,25 @@ notes:
|
|||
- This module does B(not) use the L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) to
|
||||
communicate with the Docker daemon. It directly calls the Docker CLI program.
|
||||
"""
|
||||
|
||||
API_ENVIRONMENT_DOCUMENTATION = r"""
|
||||
options:
|
||||
docker_host:
|
||||
env:
|
||||
- name: DOCKER_HOST
|
||||
tls_hostname:
|
||||
env:
|
||||
- name: DOCKER_TLS_HOSTNAME
|
||||
api_version:
|
||||
env:
|
||||
- name: DOCKER_API_VERSION
|
||||
timeout:
|
||||
env:
|
||||
- name: DOCKER_TIMEOUT
|
||||
tls:
|
||||
env:
|
||||
- name: DOCKER_TLS
|
||||
validate_certs:
|
||||
env:
|
||||
- name: DOCKER_TLS_VERIFY
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ author:
|
|||
extends_documentation_fragment:
|
||||
- ansible.builtin.constructed
|
||||
- community.docker._docker.api_documentation
|
||||
- community.docker._docker.api_environment_documentation
|
||||
- community.library_inventory_filtering_v1.inventory_filter
|
||||
description:
|
||||
- Reads inventories from the Docker API.
|
||||
|
|
|
|||
|
|
@ -388,7 +388,7 @@ class APIClient(_Session):
|
|||
@t.overload
|
||||
def _stream_helper(
|
||||
self, response: Response, *, decode: t.Literal[False] = False
|
||||
) -> t.Generator[bytes]: ...
|
||||
) -> t.Generator[bytes | str]: ...
|
||||
|
||||
@t.overload
|
||||
def _stream_helper(
|
||||
|
|
@ -795,7 +795,7 @@ class APIClient(_Session):
|
|||
data: t.Any = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
**kwargs: t.Any,
|
||||
) -> SocketLike:
|
||||
) -> tuple[SocketLike, Response]:
|
||||
headers = headers.copy() if headers else {}
|
||||
headers.update(
|
||||
{
|
||||
|
|
@ -803,15 +803,14 @@ class APIClient(_Session):
|
|||
"Upgrade": "tcp",
|
||||
}
|
||||
)
|
||||
return self._get_raw_response_socket(
|
||||
self._post_json(
|
||||
self._url(pathfmt, *args, versioned_api=True),
|
||||
data,
|
||||
headers=headers,
|
||||
stream=True,
|
||||
**kwargs,
|
||||
)
|
||||
response = self._post_json(
|
||||
self._url(pathfmt, *args, versioned_api=True),
|
||||
data,
|
||||
headers=headers,
|
||||
stream=True,
|
||||
**kwargs,
|
||||
)
|
||||
return self._get_raw_response_socket(response), response
|
||||
|
||||
@t.overload
|
||||
def post_json_to_stream(
|
||||
|
|
|
|||
|
|
@ -117,7 +117,9 @@ class UnixHTTPAdapter(BaseHTTPAdapter):
|
|||
|
||||
return pool
|
||||
|
||||
def request_url(self, request: PreparedRequest, proxies: Mapping[str, str]) -> str:
|
||||
def request_url(
|
||||
self, request: PreparedRequest, proxies: Mapping[str, str] | None
|
||||
) -> str:
|
||||
# The select_proxy utility in requests errors out when the provided URL
|
||||
# does not have a hostname, like is the case when using a UNIX socket.
|
||||
# Since proxies are an irrelevant notion in the case of UNIX sockets
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ def frames_iter_no_tty(socket: SocketLike) -> t.Generator[tuple[int, bytes]]:
|
|||
not enabled.
|
||||
"""
|
||||
while True:
|
||||
(stream, n) = next_frame_header(socket)
|
||||
stream, n = next_frame_header(socket)
|
||||
if n < 0:
|
||||
break
|
||||
while n > 0:
|
||||
|
|
|
|||
|
|
@ -523,7 +523,10 @@ class AnsibleDockerClientBase(Client):
|
|||
) -> bool:
|
||||
if img1 is None or img2 is None:
|
||||
return img1 == img2
|
||||
filter_keys = {"Metadata"}
|
||||
filter_keys = {"Metadata", "Identity"}
|
||||
# Since Docker 29.2 (?) the order of entries in Identity.Pull can change
|
||||
# even though the image is the same. Since we're only really interested
|
||||
# in the image's Id field, we simply skip all of Identity.
|
||||
img1_filtered = {k: v for k, v in img1.items() if k not in filter_keys}
|
||||
img2_filtered = {k: v for k, v in img2.items() if k not in filter_keys}
|
||||
return img1_filtered == img2_filtered
|
||||
|
|
@ -561,7 +564,7 @@ class AnsibleDockerClientBase(Client):
|
|||
self._raise_for_status(response)
|
||||
for line in self._stream_helper(response, decode=True):
|
||||
self.log(line, pretty_print=True)
|
||||
if line.get("error"):
|
||||
if line.get("error") or line.get("errorDetail"):
|
||||
if line.get("errorDetail"):
|
||||
error_detail = line.get("errorDetail")
|
||||
self.fail(
|
||||
|
|
|
|||
|
|
@ -186,12 +186,15 @@ class AnsibleDockerClientBase:
|
|||
cwd: str | None = None,
|
||||
environ_update: dict[str, str] | None = None,
|
||||
warn_on_stderr: bool = False,
|
||||
parse_empty_as_none: bool = False,
|
||||
) -> tuple[int, t.Any, bytes]:
|
||||
rc, stdout, stderr = self.call_cli(
|
||||
*args, check_rc=check_rc, data=data, cwd=cwd, environ_update=environ_update
|
||||
)
|
||||
if warn_on_stderr and stderr:
|
||||
self.warn(to_text(stderr))
|
||||
if parse_empty_as_none and not stdout.strip():
|
||||
return rc, None, stderr
|
||||
try:
|
||||
data = json.loads(stdout)
|
||||
except Exception as exc: # pylint: disable=broad-exception-caught
|
||||
|
|
|
|||
|
|
@ -707,7 +707,7 @@ def update_failed(
|
|||
result: dict[str, t.Any],
|
||||
events: Sequence[Event],
|
||||
args: list[str],
|
||||
stdout: str | bytes,
|
||||
stdout: str | bytes | None,
|
||||
stderr: str | bytes,
|
||||
rc: int,
|
||||
cli: str,
|
||||
|
|
@ -739,7 +739,7 @@ def update_failed(
|
|||
result["failed"] = True
|
||||
result["msg"] = "\n".join(errors)
|
||||
result["cmd"] = " ".join(quote(arg) for arg in [cli] + args)
|
||||
result["stdout"] = to_text(stdout)
|
||||
result["stdout"] = to_text(stdout) if stdout is not None else ""
|
||||
result["stderr"] = to_text(stderr)
|
||||
result["rc"] = rc
|
||||
return True
|
||||
|
|
@ -900,7 +900,7 @@ class BaseComposeManager(DockerBaseClass):
|
|||
return args
|
||||
|
||||
def _handle_failed_cli_call(
|
||||
self, args: list[str], rc: int, stdout: str | bytes, stderr: bytes
|
||||
self, args: list[str], rc: int, stdout: str | bytes | None, stderr: bytes
|
||||
) -> t.NoReturn:
|
||||
events = parse_json_events(stderr, warn_function=self.client.warn)
|
||||
result: dict[str, t.Any] = {}
|
||||
|
|
@ -945,15 +945,20 @@ class BaseComposeManager(DockerBaseClass):
|
|||
def list_images(self) -> list[str]:
|
||||
args = self.get_base_args() + ["images", "--format", "json"]
|
||||
rc, images, stderr = self.client.call_cli_json(
|
||||
*args, cwd=self.project_src, check_rc=not self.use_json_events
|
||||
*args,
|
||||
cwd=self.project_src,
|
||||
check_rc=not self.use_json_events,
|
||||
parse_empty_as_none=True,
|
||||
)
|
||||
if self.use_json_events and rc != 0:
|
||||
self._handle_failed_cli_call(args, rc, images, stderr)
|
||||
if images is None:
|
||||
return []
|
||||
if isinstance(images, dict):
|
||||
# Handle breaking change in Docker Compose 2.37.0; see
|
||||
# https://github.com/ansible-collections/community.docker/issues/1082
|
||||
# and https://github.com/docker/compose/issues/12916 for details
|
||||
images = list(images.values())
|
||||
return list(images.values())
|
||||
return images
|
||||
|
||||
def parse_events(
|
||||
|
|
@ -994,7 +999,7 @@ class BaseComposeManager(DockerBaseClass):
|
|||
result: dict[str, t.Any],
|
||||
events: Sequence[Event],
|
||||
args: list[str],
|
||||
stdout: str | bytes,
|
||||
stdout: str | bytes | None,
|
||||
stderr: bytes,
|
||||
rc: int,
|
||||
) -> bool:
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@ options:
|
|||
type: bool
|
||||
default: false
|
||||
version_added: 3.12.0
|
||||
ignore_pull_failures:
|
||||
description:
|
||||
- If set to V(true), will pull what it can and ignores images with pull failures.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 5.1.0
|
||||
include_deps:
|
||||
description:
|
||||
- If set to V(true), also pull services that are declared as dependencies.
|
||||
|
|
@ -132,6 +138,7 @@ class PullManager(BaseComposeManager):
|
|||
|
||||
self.policy: t.Literal["always", "missing"] = parameters["policy"]
|
||||
self.ignore_buildable: bool = parameters["ignore_buildable"]
|
||||
self.ignore_pull_failures: bool = parameters["ignore_pull_failures"]
|
||||
self.include_deps: bool = parameters["include_deps"]
|
||||
self.services: list[str] = parameters["services"] or []
|
||||
|
||||
|
|
@ -152,6 +159,8 @@ class PullManager(BaseComposeManager):
|
|||
args.extend(["--policy", self.policy])
|
||||
if self.ignore_buildable:
|
||||
args.append("--ignore-buildable")
|
||||
if self.ignore_pull_failures:
|
||||
args.append("--ignore-pull-failures")
|
||||
if self.include_deps:
|
||||
args.append("--include-deps")
|
||||
if dry_run:
|
||||
|
|
@ -187,6 +196,7 @@ def main() -> None:
|
|||
"default": "always",
|
||||
},
|
||||
"ignore_buildable": {"type": "bool", "default": False},
|
||||
"ignore_pull_failures": {"type": "bool", "default": False},
|
||||
"include_deps": {"type": "bool", "default": False},
|
||||
"services": {"type": "list", "elements": "str"},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ def main() -> None:
|
|||
stdout: bytes | None
|
||||
stderr: bytes | None
|
||||
if stdin and not detach:
|
||||
exec_socket = client.post_json_to_stream_socket(
|
||||
exec_socket, response = client.post_json_to_stream_socket(
|
||||
"/exec/{0}/start", exec_id, data=data
|
||||
)
|
||||
try:
|
||||
|
|
@ -286,7 +286,7 @@ def main() -> None:
|
|||
|
||||
stdout, stderr = exec_socket_handler.consume()
|
||||
finally:
|
||||
exec_socket.close()
|
||||
response.close()
|
||||
elif tty:
|
||||
stdout, stderr = client.post_json_to_stream(
|
||||
"/exec/{0}/start",
|
||||
|
|
|
|||
|
|
@ -995,7 +995,7 @@ class ImageManager(DockerBaseClass):
|
|||
self.log(line, pretty_print=True)
|
||||
self._extract_output_line(line, build_output)
|
||||
|
||||
if line.get("error"):
|
||||
if line.get("error") or line.get("errorDetail"):
|
||||
if line.get("errorDetail"):
|
||||
error_detail = line.get("errorDetail")
|
||||
self.fail(
|
||||
|
|
|
|||
|
|
@ -31,9 +31,12 @@ attributes:
|
|||
details:
|
||||
- Whether the module is idempotent depends on the storage API used for images,
|
||||
which determines how the image ID is computed. The idempotency check needs
|
||||
that the image ID equals the ID stored in archive's C(manifest.json).
|
||||
the image ID to equal the ID stored in archive's C(manifest.json).
|
||||
This seemed to have worked fine with the default storage backend up to Docker 28,
|
||||
but seems to have changed in Docker 29.
|
||||
- This module is B(not idempotent) when used with multi-architecture images,
|
||||
regardless of Docker version.
|
||||
- Full idempotency requires Docker 28 or earlier B(and) a single-architecture image.
|
||||
|
||||
options:
|
||||
names:
|
||||
|
|
@ -61,6 +64,13 @@ options:
|
|||
- Export the image even if the C(.tar) file already exists and seems to contain the right image.
|
||||
type: bool
|
||||
default: false
|
||||
platform:
|
||||
description:
|
||||
- Ask for this specific platform when exporting.
|
||||
- For example, C(linux/amd64), C(linux/arm64).
|
||||
- Requires Docker API 1.48 or newer.
|
||||
type: str
|
||||
version_added: 5.2.0
|
||||
|
||||
requirements:
|
||||
- "Docker API >= 1.25"
|
||||
|
|
@ -98,6 +108,7 @@ images:
|
|||
sample: []
|
||||
"""
|
||||
|
||||
import json
|
||||
import traceback
|
||||
import typing as t
|
||||
|
||||
|
|
@ -119,6 +130,9 @@ from ansible_collections.community.docker.plugins.module_utils._image_archive im
|
|||
api_image_id,
|
||||
load_archived_image_manifest,
|
||||
)
|
||||
from ansible_collections.community.docker.plugins.module_utils._platform import (
|
||||
_Platform,
|
||||
)
|
||||
from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
DockerBaseClass,
|
||||
is_image_name_id,
|
||||
|
|
@ -137,6 +151,7 @@ class ImageExportManager(DockerBaseClass):
|
|||
self.path = parameters["path"]
|
||||
self.force = parameters["force"]
|
||||
self.tag = parameters["tag"]
|
||||
self.platform = parameters["platform"]
|
||||
|
||||
if not is_valid_tag(self.tag, allow_empty=True):
|
||||
self.fail(f'"{self.tag}" is not a valid docker tag')
|
||||
|
|
@ -198,15 +213,31 @@ class ImageExportManager(DockerBaseClass):
|
|||
except Exception as exc: # pylint: disable=broad-exception-caught
|
||||
self.fail(f"Error writing image archive {self.path} - {exc}")
|
||||
|
||||
def _platform_param(self) -> str:
|
||||
platform = _Platform.parse_platform_string(self.platform)
|
||||
platform_spec: dict[str, str] = {}
|
||||
if platform.os:
|
||||
platform_spec["os"] = platform.os
|
||||
if platform.arch:
|
||||
platform_spec["architecture"] = platform.arch
|
||||
if platform.variant:
|
||||
platform_spec["variant"] = platform.variant
|
||||
return json.dumps(platform_spec)
|
||||
|
||||
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:
|
||||
self.log(f"Getting archive of image {image_names[0]}")
|
||||
params: dict[str, t.Any] = {}
|
||||
if self.platform:
|
||||
params["platform"] = self._platform_param()
|
||||
try:
|
||||
chunks = self.client._stream_raw_result(
|
||||
self.client._get(
|
||||
self.client._url("/images/{0}/get", image_names[0]), stream=True
|
||||
self.client._url("/images/{0}/get", image_names[0]),
|
||||
stream=True,
|
||||
params=params,
|
||||
),
|
||||
chunk_size=DEFAULT_DATA_CHUNK_SIZE,
|
||||
decode=False,
|
||||
|
|
@ -215,12 +246,15 @@ class ImageExportManager(DockerBaseClass):
|
|||
self.fail(f"Error getting image {image_names[0]} - {exc}")
|
||||
else:
|
||||
self.log(f"Getting archive of images {image_names_str}")
|
||||
params = {"names": image_names}
|
||||
if self.platform:
|
||||
params["platform"] = self._platform_param()
|
||||
try:
|
||||
chunks = self.client._stream_raw_result(
|
||||
self.client._get(
|
||||
self.client._url("/images/get"),
|
||||
stream=True,
|
||||
params={"names": image_names},
|
||||
params=params,
|
||||
),
|
||||
chunk_size=DEFAULT_DATA_CHUNK_SIZE,
|
||||
decode=False,
|
||||
|
|
@ -277,11 +311,17 @@ def main() -> None:
|
|||
"aliases": ["name"],
|
||||
},
|
||||
"tag": {"type": "str", "default": "latest"},
|
||||
"platform": {"type": "str"},
|
||||
}
|
||||
|
||||
option_minimal_versions = {
|
||||
"platform": {"docker_api_version": "1.48"},
|
||||
}
|
||||
|
||||
client = AnsibleDockerClient(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
option_minimal_versions=option_minimal_versions,
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ class DockerFileStore:
|
|||
if not server_creds:
|
||||
raise CredentialsNotFound("No matching credentials")
|
||||
|
||||
(username, password) = decode_auth(server_creds["auth"])
|
||||
username, password = decode_auth(server_creds["auth"])
|
||||
|
||||
return {"Username": username, "Secret": password}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ options:
|
|||
description:
|
||||
- List arguments to be passed to the container.
|
||||
- Corresponds to the C(ARG) parameter of C(docker service create).
|
||||
- When O(command_as_args=true), these values are appended after O(command)
|
||||
and both are sent as ContainerSpec.Args (matching the Docker CLI).
|
||||
- When O(command_as_args=false), O(args) is sent as C(ContainerSpec.Args)
|
||||
while O(command) is sent as ContainerSpec.Command.
|
||||
type: list
|
||||
elements: str
|
||||
command:
|
||||
|
|
@ -43,7 +47,28 @@ options:
|
|||
- Command to execute when the container starts.
|
||||
- A command may be either a string or a list or a list of strings.
|
||||
- Corresponds to the C(COMMAND) parameter of C(docker service create).
|
||||
- When O(command_as_args=false), this is sent as C(ContainerSpec.Command),
|
||||
which replaces the image C(ENTRYPOINT). This is the historical module behavior.
|
||||
- When O(command_as_args=true), this is combined with O(args) and sent as
|
||||
C(ContainerSpec.Args) (same as the Docker CLI
|
||||
C(docker service create IMAGE [COMMAND] [ARG...])), so the image C(ENTRYPOINT)
|
||||
is preserved. Use this for flag-style commands (for example V(--config.file=...))
|
||||
that must be passed as arguments to the image entrypoint
|
||||
(see U(https://github.com/ansible-collections/community.docker/issues/1044)).
|
||||
type: raw
|
||||
command_as_args:
|
||||
description:
|
||||
- Controls how O(command) and O(args) are mapped to the service C(ContainerSpec).
|
||||
- If V(false) (default), O(command) is written to C(ContainerSpec.Command) and
|
||||
O(args) to C(ContainerSpec.Args). This matches the historical module behavior
|
||||
(ContainerSpec.Command replaces the image C(ENTRYPOINT)).
|
||||
- If V(true), O(command) and O(args) are concatenated and written only to
|
||||
C(ContainerSpec.Args), matching C(docker service create IMAGE [COMMAND] [ARG...])
|
||||
so the image C(ENTRYPOINT) is preserved.
|
||||
- The current default will eventually be depreacted and change to V(true).
|
||||
type: bool
|
||||
default: false
|
||||
version_added: 5.3.0
|
||||
configs:
|
||||
description:
|
||||
- List of dictionaries describing the service configs.
|
||||
|
|
@ -696,7 +721,7 @@ rebuilt:
|
|||
|
||||
EXAMPLES = r"""
|
||||
---
|
||||
- name: Set command and arguments
|
||||
- name: Set command and arguments (historical mapping)
|
||||
community.docker.docker_swarm_service:
|
||||
name: myservice
|
||||
image: alpine
|
||||
|
|
@ -704,6 +729,14 @@ EXAMPLES = r"""
|
|||
args:
|
||||
- "3600"
|
||||
|
||||
- name: Pass flags to the image ENTRYPOINT (CLI-compatible mapping)
|
||||
community.docker.docker_swarm_service:
|
||||
name: loki
|
||||
image: grafana/loki:main
|
||||
command_as_args: true
|
||||
command:
|
||||
- '-config.file=/etc/loki/loki-config.yaml'
|
||||
|
||||
- name: Set a bind mount
|
||||
community.docker.docker_swarm_service:
|
||||
name: myservice
|
||||
|
|
@ -1054,6 +1087,22 @@ def has_dict_changed(
|
|||
return False
|
||||
|
||||
|
||||
def _combine_command_args(
|
||||
command: list[str] | None, args: list[str] | None
|
||||
) -> list[str] | None:
|
||||
"""
|
||||
Combine ansible command + args into a single argv list.
|
||||
|
||||
Matches docker CLI ``service create IMAGE [COMMAND] [ARG...]``, which places
|
||||
all post-image tokens into ContainerSpec.Args (preserving ENTRYPOINT).
|
||||
"""
|
||||
if command is None:
|
||||
return args
|
||||
if args is None:
|
||||
return command
|
||||
return command + args
|
||||
|
||||
|
||||
def has_list_changed(
|
||||
new_list: list[t.Any] | None,
|
||||
old_list: list[t.Any] | None,
|
||||
|
|
@ -1161,6 +1210,7 @@ class DockerService(DockerBaseClass):
|
|||
self.image: str | None = ""
|
||||
self.command: t.Any = None
|
||||
self.args: list[str] | None = None
|
||||
self.command_as_args: bool = False
|
||||
self.endpoint_mode: t.Literal["vip", "dnsrr"] | None = None
|
||||
self.dns: list[str] | None = None
|
||||
self.healthcheck: dict[str, t.Any] | None = None
|
||||
|
|
@ -1473,6 +1523,7 @@ class DockerService(DockerBaseClass):
|
|||
s = DockerService(client.docker_api_version, client.docker_py_version)
|
||||
s.image = image_digest
|
||||
s.args = ap["args"]
|
||||
s.command_as_args = ap["command_as_args"]
|
||||
s.endpoint_mode = ap["endpoint_mode"]
|
||||
s.dns = ap["dns"]
|
||||
s.dns_search = ap["dns_search"]
|
||||
|
|
@ -1675,10 +1726,21 @@ class DockerService(DockerBaseClass):
|
|||
needs_rebuild = not self.can_update_networks
|
||||
if self.replicas != os.replicas:
|
||||
differences.add("replicas", parameter=self.replicas, active=os.replicas)
|
||||
if has_list_changed(self.command, os.command, sort_lists=False):
|
||||
differences.add("command", parameter=self.command, active=os.command)
|
||||
if has_list_changed(self.args, os.args, sort_lists=False):
|
||||
differences.add("args", parameter=self.args, active=os.args)
|
||||
if self.command_as_args:
|
||||
# command + args are stored as ContainerSpec.Args (CLI-compatible).
|
||||
# Older module versions wrote command to ContainerSpec.Command,
|
||||
# so compare the combined command line from either field.
|
||||
desired_cmdline = _combine_command_args(self.command, self.args)
|
||||
active_cmdline = _combine_command_args(os.command, os.args)
|
||||
if has_list_changed(desired_cmdline, active_cmdline, sort_lists=False):
|
||||
differences.add(
|
||||
"command", parameter=desired_cmdline, active=active_cmdline
|
||||
)
|
||||
else:
|
||||
if has_list_changed(self.command, os.command, sort_lists=False):
|
||||
differences.add("command", parameter=self.command, active=os.command)
|
||||
if has_list_changed(self.args, os.args, sort_lists=False):
|
||||
differences.add("args", parameter=self.args, active=os.args)
|
||||
if has_list_changed(self.constraints, os.constraints):
|
||||
differences.add(
|
||||
"constraints", parameter=self.constraints, active=os.constraints
|
||||
|
|
@ -1999,10 +2061,20 @@ class DockerService(DockerBaseClass):
|
|||
dns_config = types.DNSConfig(**dns_config_args) if dns_config_args else None
|
||||
|
||||
container_spec_args: dict[str, t.Any] = {}
|
||||
if self.command is not None:
|
||||
container_spec_args["command"] = self.command
|
||||
if self.args is not None:
|
||||
container_spec_args["args"] = self.args
|
||||
if self.command_as_args:
|
||||
# Match `docker service create IMAGE [COMMAND] [ARG...]`: the CLI places
|
||||
# all post-image tokens into ContainerSpec.Args so ENTRYPOINT is kept.
|
||||
# Writing Command instead replaces ENTRYPOINT and breaks flag-style
|
||||
# commands such as `-config.file=...` (see #1044, #212).
|
||||
combined_args = _combine_command_args(self.command, self.args)
|
||||
if combined_args is not None:
|
||||
container_spec_args["args"] = combined_args
|
||||
else:
|
||||
# Historical mapping: command -> ContainerSpec.Command, args -> Args.
|
||||
if self.command is not None:
|
||||
container_spec_args["command"] = self.command
|
||||
if self.args is not None:
|
||||
container_spec_args["args"] = self.args
|
||||
if self.env is not None:
|
||||
container_spec_args["env"] = self.env
|
||||
if self.user is not None:
|
||||
|
|
@ -2748,6 +2820,7 @@ def main() -> None:
|
|||
"networks": {"type": "list", "elements": "raw"},
|
||||
"command": {"type": "raw"},
|
||||
"args": {"type": "list", "elements": "str"},
|
||||
"command_as_args": {"type": "bool", "default": False},
|
||||
"env": {"type": "raw"},
|
||||
"env_files": {"type": "list", "elements": "path"},
|
||||
"force_update": {"type": "bool", "default": False},
|
||||
|
|
|
|||
Loading…
Reference in a new issue