From 5aabb5ea026be09ddaaff6164e0233b954c3bd2c Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 4 Mar 2019 12:10:09 +0100 Subject: [PATCH] docker_swarm: support older docker-py versions (#53129) * Decreasing docker_swarm requirements. * Fixing docker-py / docker API version requirements, and some comments. * Add changelog. * Only send parameters specified by user to docker daemon. * Extend labels test: not specifying == keep labels. * Bump minimally required docker-py version for docker_node and docker_node_facts to 2.4.0. * Prevent crashing when publish or healthcheck is not provided. * Similarly to docker_swarm tests, only execute docker_node tests on real VMs and restart docker daemon when tests are done. (cherry picked from commit 8e26c2dfbe6b7cbfaf5fa3b2b5ce72d28a2e8319) --- .../53129-docker_swarm-older-docker-py.yaml | 2 + .../modules/cloud/docker/docker_swarm.py | 89 +++++++----- .../targets/docker_secret/tasks/main.yml | 5 +- .../targets/docker_swarm/tasks/main.yml | 4 +- .../docker_swarm/tasks/tests/basic.yml | 4 +- .../docker_swarm/tasks/tests/options-ca.yml | 12 ++ .../docker_swarm/tasks/tests/options.yml | 137 ++++++++++++++++-- 7 files changed, 197 insertions(+), 56 deletions(-) create mode 100644 changelogs/fragments/53129-docker_swarm-older-docker-py.yaml diff --git a/changelogs/fragments/53129-docker_swarm-older-docker-py.yaml b/changelogs/fragments/53129-docker_swarm-older-docker-py.yaml new file mode 100644 index 0000000000..460eb0cef6 --- /dev/null +++ b/changelogs/fragments/53129-docker_swarm-older-docker-py.yaml @@ -0,0 +1,2 @@ +bugfixes: +- "docker_swarm - now supports docker-py 1.10.0 and newer, instead only docker 2.6.0 and newer." diff --git a/lib/ansible/modules/cloud/docker/docker_swarm.py b/lib/ansible/modules/cloud/docker/docker_swarm.py index b32ce1295f..153bc8385c 100644 --- a/lib/ansible/modules/cloud/docker/docker_swarm.py +++ b/lib/ansible/modules/cloud/docker/docker_swarm.py @@ -125,20 +125,26 @@ options: - User-defined key/value metadata. - Label operations in this module apply to the docker swarm cluster. Use M(docker_node) module to add/modify/remove swarm node labels. + - Requires API version >= 1.32. type: dict signing_ca_cert: description: - The desired signing CA certificate for all swarm node TLS leaf certificates, in PEM format. - type: path + - This must not be a path to a certificate, but the contents of the certificate. + - Requires API version >= 1.30. + type: str signing_ca_key: description: - The desired signing CA key for all swarm node TLS leaf certificates, in PEM format. - type: path + - This must not be a path to a key, but the contents of the key. + - Requires API version >= 1.30. + type: str ca_force_rotate: description: - An integer whose purpose is to force swarm to generate a new signing CA certificate and key, if none have been specified. - Docker default value is C(0). + - Requires API version >= 1.30. type: int autolock_managers: description: @@ -157,11 +163,15 @@ extends_documentation_fragment: - docker requirements: - python >= 2.7 - - "docker-py >= 2.6.0" + - "docker-py >= 1.10.0" - "Please note that the L(docker-py,https://pypi.org/project/docker-py/) Python module has been superseded by L(docker,https://pypi.org/project/docker/) (see L(here,https://github.com/docker/docker-py/issues/1310) for details). - Version 2.1.0 or newer is only available with the C(docker) module." + For Python 2.6, C(docker-py) must be used. Otherwise, it is recommended to + install the C(docker) Python module. Note that both modules should I(not) + be installed at the same time. Also note that when both modules are installed + and one of them is uninstalled, the other might no longer function and a + reinstall of it is required." - Docker API >= 1.25 author: - Thierry Bouvet (@tbouvet) @@ -268,7 +278,6 @@ class TaskParameters(DockerBaseClass): self.election_tick = None self.dispatcher_heartbeat_period = None self.node_cert_expiry = None - self.external_cas = None self.name = None self.labels = None self.log_driver = None @@ -286,8 +295,6 @@ class TaskParameters(DockerBaseClass): if key in result.__dict__: setattr(result, key, value) - result.labels = result.labels or {} - result.update_parameters(client) return result @@ -334,34 +341,43 @@ class TaskParameters(DockerBaseClass): self.log_driver = spec['TaskDefaults']['LogDriver'] def update_parameters(self, client): - params = dict( - snapshot_interval=self.snapshot_interval, - task_history_retention_limit=self.task_history_retention_limit, - keep_old_snapshots=self.keep_old_snapshots, - log_entries_for_slow_followers=self.log_entries_for_slow_followers, - heartbeat_tick=self.heartbeat_tick, - election_tick=self.election_tick, - dispatcher_heartbeat_period=self.dispatcher_heartbeat_period, - node_cert_expiry=self.node_cert_expiry, - name=self.name, - signing_ca_cert=self.signing_ca_cert, - signing_ca_key=self.signing_ca_key, - ca_force_rotate=self.ca_force_rotate, - autolock_managers=self.autolock_managers, - log_driver=self.log_driver, + assign = dict( + snapshot_interval='snapshot_interval', + task_history_retention_limit='task_history_retention_limit', + keep_old_snapshots='keep_old_snapshots', + log_entries_for_slow_followers='log_entries_for_slow_followers', + heartbeat_tick='heartbeat_tick', + election_tick='election_tick', + dispatcher_heartbeat_period='dispatcher_heartbeat_period', + node_cert_expiry='node_cert_expiry', + name='name', + labels='labels', + signing_ca_cert='signing_ca_cert', + signing_ca_key='signing_ca_key', + ca_force_rotate='ca_force_rotate', + autolock_managers='autolock_managers', + log_driver='log_driver', ) - if self.labels: - params['labels'] = self.labels + params = dict() + for dest, source in assign.items(): + if not client.option_minimal_versions[source]['supported']: + continue + value = getattr(self, source) + if value is not None: + params[dest] = value self.spec = client.create_swarm_spec(**params) - def compare_to_active(self, other): + def compare_to_active(self, other, client): for k in self.__dict__: if k in ('advertise_addr', 'listen_addr', 'remote_addrs', 'join_token', 'rotate_worker_token', 'rotate_manager_token', 'spec'): continue - if self.__dict__[k] is None: + if not client.option_minimal_versions[k]['supported']: continue - if self.__dict__[k] != other.__dict__[k]: + value = getattr(self, k) + if value is None: + continue + if value != getattr(other, k): return False if self.rotate_worker_token: return False @@ -441,14 +457,15 @@ class SwarmManager(DockerBaseClass): self.parameters.update_from_swarm_info(self.swarm_info) old_parameters = TaskParameters() old_parameters.update_from_swarm_info(self.swarm_info) - if self.parameters.compare_to_active(old_parameters): + if self.parameters.compare_to_active(old_parameters, self.client): self.results['actions'].append("No modification") self.results['changed'] = False return - self.parameters.update_parameters(self.client) + update_parameters = TaskParameters.from_ansible_params(self.client) + update_parameters.update_parameters(self.client) if not self.check_mode: self.client.update_swarm( - version=version, swarm_spec=self.parameters.spec, + version=version, swarm_spec=update_parameters.spec, rotate_worker_token=self.parameters.rotate_worker_token, rotate_manager_token=self.parameters.rotate_manager_token) except APIError as exc: @@ -567,17 +584,19 @@ def main(): ] option_minimal_versions = dict( - labels=dict(docker_api_version='1.32'), - signing_ca_cert=dict(docker_api_version='1.30'), - signing_ca_key=dict(docker_api_version='1.30'), - ca_force_rotate=dict(docker_api_version='1.30'), + labels=dict(docker_py_version='2.6.0', docker_api_version='1.32'), + signing_ca_cert=dict(docker_py_version='2.6.0', docker_api_version='1.30'), + signing_ca_key=dict(docker_py_version='2.6.0', docker_api_version='1.30'), + ca_force_rotate=dict(docker_py_version='2.6.0', docker_api_version='1.30'), + autolock_managers=dict(docker_py_version='2.6.0'), + log_driver=dict(docker_py_version='2.6.0'), ) client = AnsibleDockerClient( argument_spec=argument_spec, supports_check_mode=True, required_if=required_if, - min_docker_version='2.6.0', + min_docker_version='1.10.0', min_docker_api_version='1.25', option_minimal_versions=option_minimal_versions, ) diff --git a/test/integration/targets/docker_secret/tasks/main.yml b/test/integration/targets/docker_secret/tasks/main.yml index 5ea5c36d7f..fe38bccdb2 100644 --- a/test/integration/targets/docker_secret/tasks/main.yml +++ b/test/integration/targets/docker_secret/tasks/main.yml @@ -1,6 +1,5 @@ - include_tasks: test_secrets.yml - # Maximum of 2.1.0 (docker-py version for docker_secrets) and 2.6.0 (docker-py version for docker_swarm) is 2.6.0 - when: docker_py_version is version('2.6.0', '>=') and docker_api_version is version('1.25', '>=') + when: docker_py_version is version('2.1.0', '>=') and docker_api_version is version('1.25', '>=') - fail: msg="Too old docker / docker-py version to run docker_secrets tests!" - when: not(docker_py_version is version('2.6.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) + when: not(docker_py_version is version('2.1.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) diff --git a/test/integration/targets/docker_swarm/tasks/main.yml b/test/integration/targets/docker_swarm/tasks/main.yml index 0c584f854c..d453e912e9 100644 --- a/test/integration/targets/docker_swarm/tasks/main.yml +++ b/test/integration/targets/docker_swarm/tasks/main.yml @@ -25,7 +25,7 @@ state: absent force: true - when: docker_py_version is version('2.6.0', '>=') and docker_api_version is version('1.25', '>=') + when: docker_py_version is version('1.10.0', '>=') and docker_api_version is version('1.25', '>=') - fail: msg="Too old docker / docker-py version to run docker_swarm tests!" - when: not(docker_py_version is version('2.6.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) + 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) diff --git a/test/integration/targets/docker_swarm/tasks/tests/basic.yml b/test/integration/targets/docker_swarm/tasks/tests/basic.yml index 3cba57e668..634a5bbfec 100644 --- a/test/integration/targets/docker_swarm/tasks/tests/basic.yml +++ b/test/integration/targets/docker_swarm/tasks/tests/basic.yml @@ -60,14 +60,14 @@ - name: Create a Swarm cluster (force re-create) docker_swarm: state: present - advertise_addr: "{{ansible_default_ipv4.address}}" + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" force: yes register: output_5 - name: Create a Swarm cluster (force re-create, check mode) docker_swarm: state: present - advertise_addr: "{{ansible_default_ipv4.address}}" + advertise_addr: "{{ansible_default_ipv4.address | default('127.0.0.1')}}" force: yes check_mode: yes register: output_6 diff --git a/test/integration/targets/docker_swarm/tasks/tests/options-ca.yml b/test/integration/targets/docker_swarm/tasks/tests/options-ca.yml index 74e3282691..40a9689ddd 100644 --- a/test/integration/targets/docker_swarm/tasks/tests/options-ca.yml +++ b/test/integration/targets/docker_swarm/tasks/tests/options-ca.yml @@ -49,6 +49,7 @@ timeout: 120 check_mode: yes register: output_1 + ignore_errors: yes - name: signing_ca_cert and signing_ca_key docker_swarm: @@ -58,6 +59,7 @@ signing_ca_key: "{{ lookup('file', role_path ~ '/' ~ output_dir ~ '/ansible_key1.key') }}" timeout: 120 register: output_2 + ignore_errors: yes - name: Private key debug: msg="{{ lookup('file', role_path ~ '/' ~ output_dir ~ '/ansible_key1.key') }}" @@ -73,6 +75,7 @@ # signing_ca_key: "{{ lookup('file', role_path ~ '/' ~ output_dir ~ '/ansible_key1.key') }}" # timeout: 120 # register: output_3 + # ignore_errors: yes #- name: signing_ca_cert and signing_ca_key (idempotent, check mode) # docker_swarm: @@ -82,6 +85,7 @@ # timeout: 120 # check_mode: yes # register: output_4 + # ignore_errors: yes - name: signing_ca_cert and signing_ca_key (change, check mode) docker_swarm: @@ -91,6 +95,7 @@ timeout: 120 check_mode: yes register: output_5 + ignore_errors: yes - name: signing_ca_cert and signing_ca_key (change) docker_swarm: @@ -99,6 +104,7 @@ signing_ca_key: "{{ lookup('file', role_path ~ '/' ~ output_dir ~ '/ansible_key2.key') }}" timeout: 120 register: output_6 + ignore_errors: yes - name: assert signing_ca_cert and signing_ca_key assert: @@ -115,6 +121,12 @@ - 'output_5.actions[0] == "Swarm cluster updated"' - 'output_6 is changed' - 'output_6.actions[0] == "Swarm cluster updated"' + when: docker_py_version is version('2.6.0', '>=') + - assert: + that: + - output_1 is failed + - "('version is ' ~ docker_py_version ~'. Minimum version required is 2.6.0') in output_1.msg" + when: docker_py_version is version('2.6.0', '<') # https://github.com/ansible/ansible/issues/34054: openssl_certificate unusable on RHEL 7 when: pyopenssl_version.stdout is version('0.15', '>=') diff --git a/test/integration/targets/docker_swarm/tasks/tests/options.yml b/test/integration/targets/docker_swarm/tasks/tests/options.yml index 26b6ab1572..31aea27322 100644 --- a/test/integration/targets/docker_swarm/tasks/tests/options.yml +++ b/test/integration/targets/docker_swarm/tasks/tests/options.yml @@ -15,18 +15,21 @@ autolock_managers: yes check_mode: yes register: output_1 + ignore_errors: yes - name: autolock_managers docker_swarm: state: present autolock_managers: yes register: output_2 + ignore_errors: yes - name: autolock_managers (idempotent) docker_swarm: state: present autolock_managers: yes register: output_3 + ignore_errors: yes - name: autolock_managers (idempotent, check mode) docker_swarm: @@ -34,6 +37,7 @@ autolock_managers: yes check_mode: yes register: output_4 + ignore_errors: yes - name: autolock_managers (change, check mode) docker_swarm: @@ -41,14 +45,16 @@ autolock_managers: no check_mode: yes register: output_5 + ignore_errors: yes - name: autolock_managers (change) docker_swarm: state: present autolock_managers: no register: output_6 + ignore_errors: yes -- name: assert changed when remove a swarm cluster +- name: assert autolock_managers changes assert: that: - 'output_1 is changed' @@ -63,6 +69,12 @@ - 'output_5.actions[0] == "Swarm cluster updated"' - 'output_6 is changed' - 'output_6.actions[0] == "Swarm cluster updated"' + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - output_1 is failed + - "('version is ' ~ docker_py_version ~'. Minimum version required is 2.6.0') in output_1.msg" + when: docker_py_version is version('2.6.0', '<') #################################################################### ## ca_force_rotate ################################################# @@ -74,18 +86,21 @@ ca_force_rotate: 1 check_mode: yes register: output_1 + ignore_errors: yes - name: ca_force_rotate docker_swarm: state: present ca_force_rotate: 1 register: output_2 + ignore_errors: yes - name: ca_force_rotate (idempotent) docker_swarm: state: present ca_force_rotate: 1 register: output_3 + ignore_errors: yes - name: ca_force_rotate (idempotent, check mode) docker_swarm: @@ -93,6 +108,7 @@ ca_force_rotate: 1 check_mode: yes register: output_4 + ignore_errors: yes - name: ca_force_rotate (change, check mode) docker_swarm: @@ -100,14 +116,16 @@ ca_force_rotate: 0 check_mode: yes register: output_5 + ignore_errors: yes - name: ca_force_rotate (change) docker_swarm: state: present ca_force_rotate: 0 register: output_6 + ignore_errors: yes -- name: assert changed when remove a swarm cluster +- name: assert ca_force_rotate changes assert: that: - 'output_1 is changed' @@ -122,6 +140,12 @@ - 'output_5.actions[0] == "Swarm cluster updated"' - 'output_6 is changed' - 'output_6.actions[0] == "Swarm cluster updated"' + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - output_1 is failed + - "('version is ' ~ docker_py_version ~'. Minimum version required is 2.6.0') in output_1.msg" + when: docker_py_version is version('2.6.0', '<') #################################################################### ## dispatcher_heartbeat_period ##################################### @@ -166,7 +190,7 @@ dispatcher_heartbeat_period: 23 register: output_6 -- name: assert changed when remove a swarm cluster +- name: assert dispatcher_heartbeat_period changes assert: that: - 'output_1 is changed' @@ -225,7 +249,7 @@ election_tick: 5 register: output_6 -- name: assert changed when remove a swarm cluster +- name: assert election_tick changes assert: that: - 'output_1 is changed' @@ -284,7 +308,7 @@ heartbeat_tick: 3 register: output_6 -- name: assert changed when remove a swarm cluster +- name: assert heartbeat_tick changes assert: that: - 'output_1 is changed' @@ -342,7 +366,7 @@ keep_old_snapshots: 2 register: output_6 -- name: assert changed when remove a swarm cluster +- name: assert keep_old_snapshots changes assert: that: - 'output_1 is changed' @@ -369,6 +393,7 @@ b: v2 check_mode: yes register: output_1 + ignore_errors: yes - name: labels docker_swarm: @@ -377,6 +402,7 @@ a: v1 b: v2 register: output_2 + ignore_errors: yes - name: labels (idempotent) docker_swarm: @@ -385,6 +411,7 @@ a: v1 b: v2 register: output_3 + ignore_errors: yes - name: labels (idempotent, check mode) docker_swarm: @@ -394,6 +421,7 @@ b: v2 check_mode: yes register: output_4 + ignore_errors: yes - name: labels (change, check mode) docker_swarm: @@ -403,6 +431,7 @@ c: v3 check_mode: yes register: output_5 + ignore_errors: yes - name: labels (change) docker_swarm: @@ -411,8 +440,68 @@ a: v1 c: v3 register: output_6 + ignore_errors: yes -- name: assert changed when remove a swarm cluster +- name: labels (not specifying, check mode) + docker_swarm: + state: present + check_mode: yes + diff: yes + register: output_7 + ignore_errors: yes + +- name: labels (not specifying) + docker_swarm: + state: present + diff: yes + register: output_8 + ignore_errors: yes + +- name: labels (idempotency, check that labels are still there) + docker_swarm: + state: present + labels: + a: v1 + c: v3 + diff: yes + register: output_9 + ignore_errors: yes + +- name: labels (empty, check mode) + docker_swarm: + state: present + labels: {} + check_mode: yes + diff: yes + register: output_10 + ignore_errors: yes + +- name: labels (empty) + docker_swarm: + state: present + labels: {} + diff: yes + register: output_11 + ignore_errors: yes + +- name: labels (empty, idempotent, check mode) + docker_swarm: + state: present + labels: {} + check_mode: yes + diff: yes + register: output_12 + ignore_errors: yes + +- name: labels (empty, idempotent) + docker_swarm: + state: present + labels: {} + diff: yes + register: output_13 + ignore_errors: yes + +- name: assert labels changes assert: that: - 'output_1 is changed' @@ -427,6 +516,26 @@ - 'output_5.actions[0] == "Swarm cluster updated"' - 'output_6 is changed' - 'output_6.actions[0] == "Swarm cluster updated"' + - 'output_7 is not changed' + - 'output_7.actions[0] == "No modification"' + - 'output_8 is not changed' + - 'output_8.actions[0] == "No modification"' + - 'output_9 is not changed' + - 'output_9.actions[0] == "No modification"' + - 'output_10 is changed' + - 'output_10.actions[0] == "Swarm cluster updated"' + - 'output_11 is changed' + - 'output_11.actions[0] == "Swarm cluster updated"' + - 'output_12 is not changed' + - 'output_12.actions[0] == "No modification"' + - 'output_13 is not changed' + - 'output_13.actions[0] == "No modification"' + when: docker_py_version is version('2.6.0', '>=') +- assert: + that: + - output_1 is failed + - "('version is ' ~ docker_py_version ~'. Minimum version required is 2.6.0') in output_1.msg" + when: docker_py_version is version('2.6.0', '<') #################################################################### ## log_entries_for_slow_followers ################################## @@ -470,7 +579,7 @@ log_entries_for_slow_followers: 23 register: output_6 -- name: assert changed when remove a swarm cluster +- name: assert log_entries_for_slow_followers changes assert: that: - 'output_1 is changed' @@ -512,7 +621,7 @@ register: output_3 ignore_errors: yes -- name: assert changed when remove a swarm cluster +- name: assert name changes assert: that: - 'output_1 is not changed' @@ -563,7 +672,7 @@ node_cert_expiry: 8766000000000000 register: output_6 -- name: assert changed when remove a swarm cluster +- name: assert node_cert_expiry changes assert: that: - 'output_1 is changed' @@ -608,7 +717,7 @@ check_mode: yes register: output_4 -- name: assert changed when remove a swarm cluster +- name: assert rotate_manager_token changes assert: that: - 'output_1 is changed' @@ -649,7 +758,7 @@ check_mode: yes register: output_4 -- name: assert changed when remove a swarm cluster +- name: assert rotate_worker_token changes assert: that: - 'output_1 is changed' @@ -703,7 +812,7 @@ snapshot_interval: 54321 register: output_6 -- name: assert changed when remove a swarm cluster +- name: assert snapshot_interval changes assert: that: - 'output_1 is changed' @@ -761,7 +870,7 @@ task_history_retention_limit: 7 register: output_6 -- name: assert changed when remove a swarm cluster +- name: assert task_history_retention_limit changes assert: that: - 'output_1 is changed'