mirror of
https://github.com/ansible-collections/community.docker.git
synced 2025-12-15 11:32:05 +00:00
Fix IP subnet and address idempotency. (#1201)
This commit is contained in:
parent
d207643e0c
commit
3da2799e03
2
changelogs/fragments/1201-docker_network.yml
Normal file
2
changelogs/fragments/1201-docker_network.yml
Normal file
@ -0,0 +1,2 @@
|
||||
bugfixes:
|
||||
- "docker_network - fix idempotency for IPv6 addresses and networks with Docker 29.0.0 (https://github.com/ansible-collections/community.docker/pull/1201)."
|
||||
@ -528,3 +528,25 @@ def normalize_ip_address(ip_address: str | None) -> str | None:
|
||||
except ValueError:
|
||||
# Fallback for invalid addresses: simply return the input
|
||||
return ip_address
|
||||
|
||||
|
||||
@t.overload
|
||||
def normalize_ip_network(network: str) -> str: ...
|
||||
|
||||
|
||||
@t.overload
|
||||
def normalize_ip_network(network: str | None) -> str | None: ...
|
||||
|
||||
|
||||
def normalize_ip_network(network: str | None) -> str | None:
|
||||
"""
|
||||
Given a network in CIDR notation as a string, normalize it so that it can be
|
||||
used to compare networks as strings.
|
||||
"""
|
||||
if network is None:
|
||||
return None
|
||||
try:
|
||||
return ipaddress.ip_network(network).compressed
|
||||
except ValueError:
|
||||
# Fallback for invalid networks: simply return the input
|
||||
return network
|
||||
|
||||
@ -299,6 +299,8 @@ from ansible_collections.community.docker.plugins.module_utils._util import (
|
||||
DifferenceTracker,
|
||||
DockerBaseClass,
|
||||
clean_dict_booleans_for_docker_api,
|
||||
normalize_ip_address,
|
||||
normalize_ip_network,
|
||||
sanitize_labels,
|
||||
)
|
||||
|
||||
@ -360,6 +362,7 @@ def validate_cidr(cidr: str) -> t.Literal["ipv4", "ipv6"]:
|
||||
:rtype: str
|
||||
:raises ValueError: If ``cidr`` is not a valid CIDR
|
||||
"""
|
||||
# TODO: Use ipaddress for this instead of rolling your own...
|
||||
if CIDR_IPV4.match(cidr):
|
||||
return "ipv4"
|
||||
if CIDR_IPV6.match(cidr):
|
||||
@ -389,6 +392,19 @@ def dicts_are_essentially_equal(a: dict[str, t.Any], b: dict[str, t.Any]) -> boo
|
||||
return True
|
||||
|
||||
|
||||
def normalize_ipam_values(ipam_config: dict[str, t.Any]) -> dict[str, t.Any]:
|
||||
result = {}
|
||||
for key, value in ipam_config.items():
|
||||
if key in ("subnet", "iprange"):
|
||||
value = normalize_ip_network(value)
|
||||
elif key in ("gateway",):
|
||||
value = normalize_ip_address(value)
|
||||
elif key in ("aux_addresses",) and value is not None:
|
||||
value = {k: normalize_ip_address(v) for k, v in value.items()}
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
class DockerNetworkManager:
|
||||
def __init__(self, client: AnsibleDockerClient) -> None:
|
||||
self.client = client
|
||||
@ -513,24 +529,35 @@ class DockerNetworkManager:
|
||||
else:
|
||||
# Put network's IPAM config into the same format as module's IPAM config
|
||||
net_ipam_configs = []
|
||||
net_ipam_configs_normalized = []
|
||||
for net_ipam_config in net["IPAM"]["Config"]:
|
||||
config = {}
|
||||
for k, v in net_ipam_config.items():
|
||||
config[normalize_ipam_config_key(k)] = v
|
||||
net_ipam_configs.append(config)
|
||||
net_ipam_configs_normalized.append(normalize_ipam_values(config))
|
||||
# Compare lists of dicts as sets of dicts
|
||||
for idx, ipam_config in enumerate(self.parameters.ipam_config):
|
||||
ipam_config_normalized = normalize_ipam_values(ipam_config)
|
||||
net_config = {}
|
||||
for net_ipam_config in net_ipam_configs:
|
||||
if dicts_are_essentially_equal(ipam_config, net_ipam_config):
|
||||
net_config_normalized = {}
|
||||
for net_ipam_config, net_ipam_config_normalized in zip(
|
||||
net_ipam_configs, net_ipam_configs_normalized
|
||||
):
|
||||
if dicts_are_essentially_equal(
|
||||
ipam_config_normalized, net_ipam_config_normalized
|
||||
):
|
||||
net_config = net_ipam_config
|
||||
net_config_normalized = net_ipam_config_normalized
|
||||
break
|
||||
for key, value in ipam_config.items():
|
||||
if value is None:
|
||||
# due to recursive argument_spec, all keys are always present
|
||||
# (but have default value None if not specified)
|
||||
continue
|
||||
if value != net_config.get(key):
|
||||
if ipam_config_normalized[key] != net_config_normalized.get(
|
||||
key
|
||||
):
|
||||
differences.add(
|
||||
f"ipam_config[{idx}].{key}",
|
||||
parameter=value,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user