From 0df2a45ce031d856f2469fbac437aa21eb4accd4 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sat, 2 Jul 2022 22:29:17 +0200 Subject: [PATCH 1/3] Rewrite the docker_network module. --- changelogs/fragments/408-docker-api.yml | 4 + plugins/modules/docker_network.py | 101 ++++++++---------- .../targets/docker_network/tasks/main.yml | 4 +- .../docker_network/tasks/tests/ipam.yml | 7 -- .../docker_network/tasks/tests/options.yml | 10 -- 5 files changed, 52 insertions(+), 74 deletions(-) create mode 100644 changelogs/fragments/408-docker-api.yml diff --git a/changelogs/fragments/408-docker-api.yml b/changelogs/fragments/408-docker-api.yml new file mode 100644 index 000000000..9bd9d26fc --- /dev/null +++ b/changelogs/fragments/408-docker-api.yml @@ -0,0 +1,4 @@ +major_changes: + - "docker_network - no longer uses the Docker SDK for Python. It requires ``requests`` to be installed, + and depending on the features used has some more requirements. If the Docker SDK for Python is installed, + these requirements are likely met (https://github.com/ansible-collections/community.docker/pull/408)." diff --git a/plugins/modules/docker_network.py b/plugins/modules/docker_network.py index 622d52ea1..32492c920 100644 --- a/plugins/modules/docker_network.py +++ b/plugins/modules/docker_network.py @@ -146,8 +146,7 @@ type: bool extends_documentation_fragment: -- community.docker.docker -- community.docker.docker.docker_py_1_documentation +- community.docker.docker.api_documentation notes: @@ -165,7 +164,6 @@ - "Dave Bendit (@DBendit)" requirements: - - "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.10.0" - "Docker API >= 1.25" ''' @@ -254,27 +252,16 @@ from ansible.module_utils.common.text.converters import to_native -from ansible_collections.community.docker.plugins.module_utils.version import LooseVersion - -from ansible_collections.community.docker.plugins.module_utils.common import ( +from ansible_collections.community.docker.plugins.module_utils.common_api import ( AnsibleDockerClient, RequestException, - docker_version, ) from ansible_collections.community.docker.plugins.module_utils.util import ( DockerBaseClass, DifferenceTracker, clean_dict_booleans_for_docker_api, ) - -try: - from docker import utils - from docker.errors import DockerException - if LooseVersion(docker_version) >= LooseVersion('2.0.0'): - from docker.types import IPAMPool, IPAMConfig -except Exception: - # missing Docker SDK for Python handled in ansible.module_utils.docker.common - pass +from ansible_collections.community.docker.plugins.module_utils._api.errors import DockerException class TaskParameters(DockerBaseClass): @@ -496,45 +483,48 @@ def has_different_config(self, net): def create_network(self): if not self.existing_network: - params = dict( - driver=self.parameters.driver, - options=self.parameters.driver_options, - ) + data = { + 'Name': self.parameters.name, + 'Driver': self.parameters.driver, + 'Options': self.parameters.driver_options, + 'IPAM': None, + 'CheckDuplicate': None, + } + + if self.parameters.enable_ipv6: + data['EnableIPv6'] = True + if self.parameters.internal: + data['Internal'] = True + if self.parameters.scope is not None: + data['Scope'] = self.parameters.scope + if self.parameters.attachable is not None: + data['Attachable'] = self.parameters.attachable + if self.parameters.labels is not None: + data["Labels"] = self.parameters.labels ipam_pools = [] if self.parameters.ipam_config: for ipam_pool in self.parameters.ipam_config: - if LooseVersion(docker_version) >= LooseVersion('2.0.0'): - ipam_pools.append(IPAMPool(**ipam_pool)) - else: - ipam_pools.append(utils.create_ipam_pool(**ipam_pool)) + ipam_pools.append({ + 'Subnet': ipam_pool['subnet'], + 'IPRange': ipam_pool['iprange'], + 'Gateway': ipam_pool['gateway'], + 'AuxiliaryAddresses': ipam_pool['aux_addresses'], + }) if self.parameters.ipam_driver or self.parameters.ipam_driver_options or ipam_pools: - # Only add ipam parameter if a driver was specified or if IPAM parameters - # were specified. Leaving this parameter away can significantly speed up + # Only add IPAM if a driver was specified or if IPAM parameters were + # specified. Leaving this parameter away can significantly speed up # creation; on my machine creation with this option needs ~15 seconds, # and without just a few seconds. - if LooseVersion(docker_version) >= LooseVersion('2.0.0'): - params['ipam'] = IPAMConfig(driver=self.parameters.ipam_driver, - pool_configs=ipam_pools, - options=self.parameters.ipam_driver_options) - else: - params['ipam'] = utils.create_ipam_config(driver=self.parameters.ipam_driver, - pool_configs=ipam_pools) - - if self.parameters.enable_ipv6 is not None: - params['enable_ipv6'] = self.parameters.enable_ipv6 - if self.parameters.internal is not None: - params['internal'] = self.parameters.internal - if self.parameters.scope is not None: - params['scope'] = self.parameters.scope - if self.parameters.attachable is not None: - params['attachable'] = self.parameters.attachable - if self.parameters.labels: - params['labels'] = self.parameters.labels + data['IPAM'] = { + 'Driver': self.parameters.ipam_driver, + 'Config': ipam_pools or [], + 'Options': self.parameters.ipam_driver_options, + } if not self.check_mode: - resp = self.client.create_network(self.parameters.name, **params) + 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("Created network %s with driver %s" % (self.parameters.name, self.parameters.driver)) @@ -544,7 +534,7 @@ def remove_network(self): if self.existing_network: self.disconnect_all_containers() if not self.check_mode: - self.client.remove_network(self.parameters.name) + self.client.delete_call('/networks/{0}', self.parameters.name) self.results['actions'].append("Removed network %s" % (self.parameters.name,)) self.results['changed'] = True @@ -557,12 +547,14 @@ def connect_containers(self): for name in self.parameters.connected: if not self.is_container_connected(name): if not self.check_mode: - self.client.connect_container_to_network(name, self.parameters.name) + data = { + "Container": name, + "EndpointConfig": None, + } + self.client.post_json('/networks/{0}/connect', self.parameters.name, data=data) self.results['actions'].append("Connected container %s" % (name,)) self.results['changed'] = True - self.diff_tracker.add('connected.{0}'.format(name), - parameter=True, - active=False) + self.diff_tracker.add('connected.{0}'.format(name), parameter=True, active=False) def disconnect_missing(self): if not self.existing_network: @@ -584,7 +576,8 @@ def disconnect_all_containers(self): def disconnect_container(self, container_name): if not self.check_mode: - self.client.disconnect_container_from_network(container_name, self.parameters.name) + data = {"Container": container_name} + self.client.post_json('/networks/{0}/disconnect', self.parameters.name, data=data) self.results['actions'].append("Disconnected container %s" % (container_name,)) self.results['changed'] = True self.diff_tracker.add('connected.{0}'.format(container_name), @@ -648,15 +641,13 @@ def main(): ) option_minimal_versions = dict( - scope=dict(docker_py_version='2.6.0', docker_api_version='1.30'), - attachable=dict(docker_py_version='2.0.0', docker_api_version='1.26'), - ipam_driver_options=dict(docker_py_version='2.0.0'), + scope=dict(docker_api_version='1.30'), + attachable=dict(docker_api_version='1.26'), ) client = AnsibleDockerClient( argument_spec=argument_spec, supports_check_mode=True, - min_docker_version='1.10.0', # "The docker server >= 1.10.0" option_minimal_versions=option_minimal_versions, ) diff --git a/tests/integration/targets/docker_network/tasks/main.yml b/tests/integration/targets/docker_network/tasks/main.yml index 61a2fd562..d172b1dd1 100644 --- a/tests/integration/targets/docker_network/tasks/main.yml +++ b/tests/integration/targets/docker_network/tasks/main.yml @@ -42,7 +42,7 @@ force: yes loop: "{{ dnetworks }}" - when: docker_py_version is version('1.10.0', '>=') and docker_api_version is version('1.25', '>=') + when: docker_api_version is version('1.25', '>=') # FIXME: find out API version! - fail: msg="Too old docker / docker-py version to run docker_network tests!" - when: not(docker_py_version is version('1.10.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) + when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/tests/integration/targets/docker_network/tasks/tests/ipam.yml b/tests/integration/targets/docker_network/tasks/tests/ipam.yml index 7491150b6..d3fccdbd0 100644 --- a/tests/integration/targets/docker_network/tasks/tests/ipam.yml +++ b/tests/integration/targets/docker_network/tasks/tests/ipam.yml @@ -303,10 +303,3 @@ - network_1 is changed - network_2 is not changed - network_3 is changed - when: docker_py_version is version('2.0.0', '>=') -- assert: - that: - - network_1 is failed - - "('version is ' ~ docker_py_version ~ ' ') in network_1.msg" - - "'Minimum version required is 2.0.0 ' in network_1.msg" - when: docker_py_version is version('2.0.0', '<') diff --git a/tests/integration/targets/docker_network/tasks/tests/options.yml b/tests/integration/targets/docker_network/tasks/tests/options.yml index 7723cabfa..f8b74b5f5 100644 --- a/tests/integration/targets/docker_network/tasks/tests/options.yml +++ b/tests/integration/targets/docker_network/tasks/tests/options.yml @@ -144,9 +144,6 @@ state: absent force: yes - # Requirements for docker_swarm - when: docker_py_version is version('2.6.0', '>=') - #################################################################### ## attachable ###################################################### #################################################################### @@ -183,13 +180,6 @@ - attachable_1 is changed - attachable_2 is not changed - attachable_3 is changed - when: docker_py_version is version('2.0.0', '>=') -- assert: - that: - - attachable_1 is failed - - "('version is ' ~ docker_py_version ~ ' ') in attachable_1.msg" - - "'Minimum version required is 2.0.0 ' in attachable_1.msg" - when: docker_py_version is version('2.0.0', '<') #################################################################### ## labels ########################################################## From f15e88566521b57a1bb47b4f97fcf35bce67fabd Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 3 Jul 2022 09:38:12 +0200 Subject: [PATCH 2/3] Update plugins/modules/docker_network.py Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com> --- plugins/modules/docker_network.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/docker_network.py b/plugins/modules/docker_network.py index 32492c920..994771128 100644 --- a/plugins/modules/docker_network.py +++ b/plugins/modules/docker_network.py @@ -514,7 +514,7 @@ def create_network(self): if self.parameters.ipam_driver or self.parameters.ipam_driver_options or ipam_pools: # Only add IPAM if a driver was specified or if IPAM parameters were - # specified. Leaving this parameter away can significantly speed up + # specified. Leaving this parameter out can significantly speed up # creation; on my machine creation with this option needs ~15 seconds, # and without just a few seconds. data['IPAM'] = { From 7675065522a62e5b6def58925fbabf74819beb38 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 3 Jul 2022 16:31:31 +0200 Subject: [PATCH 3/3] Improve error messages. --- plugins/modules/docker_network.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/docker_network.py b/plugins/modules/docker_network.py index 994771128..ba49b6bfd 100644 --- a/plugins/modules/docker_network.py +++ b/plugins/modules/docker_network.py @@ -656,10 +656,10 @@ def main(): cm = DockerNetworkManager(client) client.module.exit_json(**cm.results) except DockerException as e: - client.fail('An unexpected docker error occurred: {0}'.format(to_native(e)), exception=traceback.format_exc()) + client.fail('An unexpected Docker error occurred: {0}'.format(to_native(e)), exception=traceback.format_exc()) except RequestException as e: client.fail( - 'An unexpected requests error occurred when Docker SDK for Python tried to talk to the docker daemon: {0}'.format(to_native(e)), + 'An unexpected requests error occurred when trying to talk to the Docker daemon: {0}'.format(to_native(e)), exception=traceback.format_exc())