From 2ddadf1e2b465251fbe47feb1fab55097bdf14db Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 23 Jul 2024 17:34:26 +0200 Subject: [PATCH] docker_container: pass networks to Daemon on container creation (#933) * Pass networks to Daemon on container creation. * Restore old behavior, and only provide all networks on creation for API 1.44+. --- .../933-docker_container-networks.yml | 12 ++++++++++ plugins/module_utils/module_container/base.py | 5 ++++- .../module_container/docker_api.py | 22 ++++++++++++++++--- .../module_utils/module_container/module.py | 14 ++++++++++-- plugins/modules/docker_container.py | 3 +++ 5 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 changelogs/fragments/933-docker_container-networks.yml diff --git a/changelogs/fragments/933-docker_container-networks.yml b/changelogs/fragments/933-docker_container-networks.yml new file mode 100644 index 000000000..5484fe613 --- /dev/null +++ b/changelogs/fragments/933-docker_container-networks.yml @@ -0,0 +1,12 @@ +minor_changes: + - "docker_container - when creating a container, directly pass all networks to connect to to the Docker Daemon + for API version 1.44 and newer. This makes creation more efficient and works around a bug in Docker Daemon that + does not use the specified MAC address in at least some cases, though only for creation + (https://github.com/ansible-collections/community.docker/pull/933)." +bugfixes: + - "docker_container - restore behavior of the module from community.docker 2.x.y that passes the first network + to the Docker Deamon while creating the container (https://github.com/ansible-collections/community.docker/pull/933)." +known_issues: + - "docker_container - when specifying a MAC address for a container's network, and the network is attached + after container creation (for example, due to idempotency checks), the MAC address is at least in some + cases ignored by the Docker Daemon (https://github.com/ansible-collections/community.docker/pull/933)." diff --git a/plugins/module_utils/module_container/base.py b/plugins/module_utils/module_container/base.py index e10acea84..3953177f6 100644 --- a/plugins/module_utils/module_container/base.py +++ b/plugins/module_utils/module_container/base.py @@ -299,8 +299,11 @@ def disconnect_container_from_network(self, client, container_id, network_id): def connect_container_to_network(self, client, container_id, network_id, parameters=None): pass + def create_container_supports_more_than_one_network(self, client): + return False + @abc.abstractmethod - def create_container(self, client, container_name, create_parameters): + def create_container(self, client, container_name, create_parameters, networks=None): pass @abc.abstractmethod diff --git a/plugins/module_utils/module_container/docker_api.py b/plugins/module_utils/module_container/docker_api.py index b130e8c7d..4e5b891cb 100644 --- a/plugins/module_utils/module_container/docker_api.py +++ b/plugins/module_utils/module_container/docker_api.py @@ -244,8 +244,8 @@ def unpause_container(self, client, container_id): def disconnect_container_from_network(self, client, container_id, network_id): client.post_json('/networks/{0}/disconnect', network_id, data={'Container': container_id}) - def connect_container_to_network(self, client, container_id, network_id, parameters=None): - parameters = (parameters or {}).copy() + def _create_endpoint_config(self, parameters): + parameters = parameters.copy() params = {} for para, dest_para in { 'ipv4_address': 'IPv4Address', @@ -268,16 +268,32 @@ def connect_container_to_network(self, client, container_id, network_id, paramet ipam_config[param] = params.pop(param) if ipam_config: params['IPAMConfig'] = ipam_config + return params + + def connect_container_to_network(self, client, container_id, network_id, parameters=None): + parameters = (parameters or {}).copy() + params = self._create_endpoint_config(parameters or {}) data = { 'Container': container_id, 'EndpointConfig': params, } client.post_json('/networks/{0}/connect', network_id, data=data) - def create_container(self, client, container_name, create_parameters): + def create_container_supports_more_than_one_network(self, client): + return client.docker_api_version >= LooseVersion('1.44') + + def create_container(self, client, container_name, create_parameters, networks=None): params = {'name': container_name} if 'platform' in create_parameters: params['platform'] = create_parameters.pop('platform') + if networks is not None: + create_parameters = create_parameters.copy() + create_parameters['NetworkingConfig'] = { + 'EndpointsConfig': dict( + (network, self._create_endpoint_config(network_params)) + for network, network_params in networks.items() + ) + } new_container = client.post_json_to_json('/containers/create', data=create_parameters, params=params) client.report_warnings(new_container) return new_container['Id'] diff --git a/plugins/module_utils/module_container/module.py b/plugins/module_utils/module_container/module.py index b58ad351a..858f4537a 100644 --- a/plugins/module_utils/module_container/module.py +++ b/plugins/module_utils/module_container/module.py @@ -766,12 +766,22 @@ def container_create(self, image): self.log("create container") self.log("image: %s parameters:" % image) self.log(create_parameters, pretty_print=True) - self.results['actions'].append(dict(created="Created container", create_parameters=create_parameters)) + networks = {} + if self.param_networks_cli_compatible and self.module.params['networks']: + network_list = self.module.params['networks'] + if not self.engine_driver.create_container_supports_more_than_one_network(self.client): + network_list = network_list[:1] + for network in network_list: + networks[network['name']] = { + key: value for key, value in network.items() + if key not in ('name', 'id') + } + self.results['actions'].append(dict(created="Created container", create_parameters=create_parameters, networks=networks)) self.results['changed'] = True new_container = None if not self.check_mode: try: - container_id = self.engine_driver.create_container(self.client, self.param_name, create_parameters) + container_id = self.engine_driver.create_container(self.client, self.param_name, create_parameters, networks=networks) except Exception as exc: self.fail("Error creating container: %s" % to_native(exc)) return self._get_container(container_id) diff --git a/plugins/modules/docker_container.py b/plugins/modules/docker_container.py index bdc92e570..832a2cf46 100644 --- a/plugins/modules/docker_container.py +++ b/plugins/modules/docker_container.py @@ -722,6 +722,9 @@ description: - Endpoint MAC address (for example, V(92:d0:c6:0a:29:33)). - This is only available for Docker API version 1.44 and later. + - Please note that when a container is attached to a network after creation, + this is currently ignored by the Docker Daemon at least in some cases. + When passed on creation, this seems to work better. type: str version_added: 3.6.0 networks_cli_compatible: