diff --git a/changelogs/fragments/52825-docker_swarm-detection-check-mode.yaml b/changelogs/fragments/52825-docker_swarm-detection-check-mode.yaml new file mode 100644 index 0000000000..2c57d9a921 --- /dev/null +++ b/changelogs/fragments/52825-docker_swarm-detection-check-mode.yaml @@ -0,0 +1,3 @@ +bugfixes: +- "docker_swarm - improve Swarm detection." +- "docker_swarm - properly implement check mode (it did apply changes)." diff --git a/lib/ansible/module_utils/docker/swarm.py b/lib/ansible/module_utils/docker/swarm.py index c3ca30c3c1..5e9cfd4ace 100644 --- a/lib/ansible/module_utils/docker/swarm.py +++ b/lib/ansible/module_utils/docker/swarm.py @@ -62,6 +62,8 @@ class AnsibleDockerSwarmClient(AnsibleDockerClient): swarm_info = json.loads(json_str) if swarm_info['Swarm']['NodeID']: return True + if swarm_info['Swarm']['LocalNodeState'] in ('active', 'pending', 'locked'): + return True return False else: try: diff --git a/lib/ansible/modules/cloud/docker/docker_swarm.py b/lib/ansible/modules/cloud/docker/docker_swarm.py index 2ae13671ad..5c8a07a54c 100644 --- a/lib/ansible/modules/cloud/docker/docker_swarm.py +++ b/lib/ansible/modules/cloud/docker/docker_swarm.py @@ -308,6 +308,7 @@ class SwarmManager(DockerBaseClass): self.client = client self.results = results self.check_mode = self.client.check_mode + self.swarm_info = {} self.parameters = TaskParameters(client) @@ -346,17 +347,18 @@ class SwarmManager(DockerBaseClass): self.__update_swarm() return - try: - self.client.init_swarm( - advertise_addr=self.parameters.advertise_addr, listen_addr=self.parameters.listen_addr, - force_new_cluster=self.parameters.force_new_cluster, swarm_spec=self.parameters.spec) - except APIError as exc: - self.client.fail("Can not create a new Swarm Cluster: %s" % to_native(exc)) + if not self.check_mode: + try: + self.client.init_swarm( + advertise_addr=self.parameters.advertise_addr, listen_addr=self.parameters.listen_addr, + force_new_cluster=self.parameters.force_new_cluster, swarm_spec=self.parameters.spec) + except APIError as exc: + self.client.fail("Can not create a new Swarm Cluster: %s" % to_native(exc)) self.__isSwarmManager() - self.results['actions'].append("New Swarm cluster created: %s" % (self.swarm_info['ID'])) + self.results['actions'].append("New Swarm cluster created: %s" % (self.swarm_info.get('ID'))) self.results['changed'] = True - self.results['swarm_facts'] = {u'JoinTokens': self.swarm_info['JoinTokens']} + self.results['swarm_facts'] = {u'JoinTokens': self.swarm_info.get('JoinTokens')} def __update_spec(self, spec): if (self.parameters.node_cert_expiry is None): @@ -406,9 +408,10 @@ class SwarmManager(DockerBaseClass): self.results['actions'].append("No modification") self.results['changed'] = False return - self.client.update_swarm( - version=version, swarm_spec=new_spec, rotate_worker_token=self.parameters.rotate_worker_token, - rotate_manager_token=self.parameters.rotate_manager_token) + if not self.check_mode: + self.client.update_swarm( + version=version, swarm_spec=new_spec, rotate_worker_token=self.parameters.rotate_worker_token, + rotate_manager_token=self.parameters.rotate_manager_token) except APIError as exc: self.client.fail("Can not update a Swarm Cluster: %s" % to_native(exc)) return @@ -424,18 +427,21 @@ class SwarmManager(DockerBaseClass): self.swarm_info = json.loads(json_str) if self.swarm_info['Swarm']['NodeID']: return True + if self.swarm_info['Swarm']['LocalNodeState'] in ('active', 'pending', 'locked'): + return True return False def join(self): if self.__isSwarmNode(): self.results['actions'].append("This node is already part of a swarm.") return - try: - self.client.join_swarm( - remote_addrs=self.parameters.remote_addrs, join_token=self.parameters.join_token, listen_addr=self.parameters.listen_addr, - advertise_addr=self.parameters.advertise_addr) - except APIError as exc: - self.client.fail("Can not join the Swarm Cluster: %s" % to_native(exc)) + if not self.check_mode: + try: + self.client.join_swarm( + remote_addrs=self.parameters.remote_addrs, join_token=self.parameters.join_token, listen_addr=self.parameters.listen_addr, + advertise_addr=self.parameters.advertise_addr) + except APIError as exc: + self.client.fail("Can not join the Swarm Cluster: %s" % to_native(exc)) self.results['actions'].append("New node is added to swarm cluster") self.results['changed'] = True @@ -443,10 +449,11 @@ class SwarmManager(DockerBaseClass): if not(self.__isSwarmNode()): self.results['actions'].append("This node is not part of a swarm.") return - try: - self.client.leave_swarm(force=self.parameters.force) - except APIError as exc: - self.client.fail("This node can not leave the Swarm Cluster: %s" % to_native(exc)) + if not self.check_mode: + try: + self.client.leave_swarm(force=self.parameters.force) + except APIError as exc: + self.client.fail("This node can not leave the Swarm Cluster: %s" % to_native(exc)) self.results['actions'].append("Node has left the swarm cluster") self.results['changed'] = True @@ -479,10 +486,11 @@ class SwarmManager(DockerBaseClass): if not(status_down): self.client.fail("Can not remove the node. The status node is ready and not down.") - try: - self.client.remove_node(node_id=self.parameters.node_id, force=self.parameters.force) - except APIError as exc: - self.client.fail("Can not remove the node from the Swarm Cluster: %s" % to_native(exc)) + if not self.check_mode: + try: + self.client.remove_node(node_id=self.parameters.node_id, force=self.parameters.force) + except APIError as exc: + self.client.fail("Can not remove the node from the Swarm Cluster: %s" % to_native(exc)) self.results['actions'].append("Node is removed from swarm cluster.") self.results['changed'] = True diff --git a/test/integration/targets/docker_config/tasks/test_docker_config.yml b/test/integration/targets/docker_config/tasks/test_docker_config.yml index d448200493..ded7eab488 100644 --- a/test/integration/targets/docker_config/tasks/test_docker_config.yml +++ b/test/integration/targets/docker_config/tasks/test_docker_config.yml @@ -1,10 +1,14 @@ --- - block: + - shell: "docker info --format '{% raw %}{{json .}}{% endraw %}' | python -m json.tool" + - name: Make sure we're not already using Docker swarm docker_swarm: state: absent force: true + - shell: "docker info --format '{% raw %}{{json .}}{% endraw %}' | python -m json.tool" + - name: Create a Swarm cluster docker_swarm: state: present diff --git a/test/integration/targets/docker_swarm/tasks/test_swarm.yml b/test/integration/targets/docker_swarm/tasks/test_swarm.yml index f683de239f..73d6737997 100644 --- a/test/integration/targets/docker_swarm/tasks/test_swarm.yml +++ b/test/integration/targets/docker_swarm/tasks/test_swarm.yml @@ -29,30 +29,73 @@ - 'output.failed' - 'output.msg == "state is remove but all of the following are missing: node_id"' + - name: Create a Swarm cluster (check mode) + docker_swarm: + state: present + check_mode: yes + register: output_1 + - name: Create a Swarm cluster docker_swarm: state: present - register: output + register: output_2 + + - name: Create a Swarm cluster (idempotent) + docker_swarm: + state: present + register: output_3 + + - name: Create a Swarm cluster (idempotent, check mode) + docker_swarm: + state: present + check_mode: yes + register: output_4 - name: assert changed when create a new swarm cluster assert: that: - - 'output.changed' - - 'output.actions[0] | regex_search("New Swarm cluster created: ")' - - 'output.swarm_facts.JoinTokens.Manager' - - 'output.swarm_facts.JoinTokens.Worker' + - 'output_1 is changed' + - 'output_2 is changed' + - 'output_2.actions[0] | regex_search("New Swarm cluster created: ")' + - 'output_2.swarm_facts.JoinTokens.Manager' + - 'output_2.swarm_facts.JoinTokens.Worker' + - 'output_3 is not changed' + - 'output_4 is not changed' + + - name: Remove a Swarm cluster (check mode) + docker_swarm: + state: absent + force: true + check_mode: yes + register: output_1 - name: Remove a Swarm cluster docker_swarm: state: absent force: true - register: output + register: output_2 + + - name: Remove a Swarm cluster (idempotent) + docker_swarm: + state: absent + force: true + register: output_3 + + - name: Remove a Swarm cluster (idempotent, check mode) + docker_swarm: + state: absent + force: true + check_mode: yes + register: output_4 - name: assert changed when remove a swarm cluster assert: that: - - 'output.changed' - - 'output.actions[0] == "Node has left the swarm cluster"' + - 'output_1 is changed' + - 'output_2 is changed' + - 'output_2.actions[0] == "Node has left the swarm cluster"' + - 'output_3 is not changed' + - 'output_4 is not changed' always: - name: Cleanup