diff --git a/changelogs/fragments/54490-docker_swarm-return-unlock-key.yaml b/changelogs/fragments/54490-docker_swarm-return-unlock-key.yaml new file mode 100644 index 00000000000..136c7e4aca6 --- /dev/null +++ b/changelogs/fragments/54490-docker_swarm-return-unlock-key.yaml @@ -0,0 +1,2 @@ +minor_changes: + - "docker_swarm - ``UnlockKey`` will now be returned when ``autolock_managers`` is ``true``." diff --git a/lib/ansible/module_utils/docker/common.py b/lib/ansible/module_utils/docker/common.py index 387df4906ca..51f70fb8d69 100644 --- a/lib/ansible/module_utils/docker/common.py +++ b/lib/ansible/module_utils/docker/common.py @@ -846,6 +846,12 @@ class DifferenceTracker(object): after[item['name']] = item['parameter'] return before, after + def has_difference_for(self, name): + ''' + Returns a boolean if a difference exists for name + ''' + return any(diff for diff in self._diff if diff['name'] == name) + def get_legacy_docker_container_diffs(self): ''' Return differences in the docker_container legacy format. diff --git a/lib/ansible/module_utils/docker/swarm.py b/lib/ansible/module_utils/docker/swarm.py index e8eec4b689a..894fe61f490 100644 --- a/lib/ansible/module_utils/docker/swarm.py +++ b/lib/ansible/module_utils/docker/swarm.py @@ -13,7 +13,10 @@ except ImportError: pass from ansible.module_utils._text import to_native -from ansible.module_utils.docker.common import AnsibleDockerClient +from ansible.module_utils.docker.common import ( + AnsibleDockerClient, + LooseVersion, +) class AnsibleDockerSwarmClient(AnsibleDockerClient): @@ -241,3 +244,8 @@ class AnsibleDockerSwarmClient(AnsibleDockerClient): def get_node_name_by_id(self, nodeid): return self.get_node_inspect(nodeid)['Description']['Hostname'] + + def get_unlock_key(self): + if self.docker_py_version < LooseVersion('2.7.0'): + return None + return super(AnsibleDockerSwarmClient, self).get_unlock_key() diff --git a/lib/ansible/modules/cloud/docker/docker_swarm.py b/lib/ansible/modules/cloud/docker/docker_swarm.py index c5cc5e17e58..9e2c71add4e 100644 --- a/lib/ansible/modules/cloud/docker/docker_swarm.py +++ b/lib/ansible/modules/cloud/docker/docker_swarm.py @@ -171,6 +171,7 @@ options: description: - If set, generate a key and use it to lock data stored on the managers. - Docker default value is C(no). + - M(docker_swarm_info) can be used to retrieve the unlock key. type: bool rotate_worker_token: description: Rotate the worker join token. @@ -250,6 +251,13 @@ swarm_facts: returned: success type: str example: SWMTKN-1--xxxxx + UnlockKey: + description: The swarm unlock-key if I(autolock_managers) is C(true). + returned: on success if I(autolock_managers) is C(true) + and swarm is initialised, or if I(autolock_managers) has changed. + type: str + example: SWMKEY-1-xxx + actions: description: Provides the actions done on the swarm. returned: when action failed. @@ -269,6 +277,7 @@ except ImportError: from ansible.module_utils.docker.common import ( DockerBaseClass, DifferenceTracker, + LooseVersion, ) from ansible.module_utils.docker.swarm import AnsibleDockerSwarmClient @@ -424,6 +433,8 @@ class SwarmManager(DockerBaseClass): self.differences = DifferenceTracker() self.parameters = TaskParameters.from_ansible_params(client) + self.created = False + def __call__(self): choice_map = { "present": self.init_swarm, @@ -450,11 +461,29 @@ class SwarmManager(DockerBaseClass): data = self.client.inspect_swarm() json_str = json.dumps(data, ensure_ascii=False) self.swarm_info = json.loads(json_str) + self.results['changed'] = False self.results['swarm_facts'] = self.swarm_info + + unlock_key = self.get_unlock_key() + self.swarm_info.update(unlock_key) except APIError: return + def get_unlock_key(self): + default = {'UnlockKey': None} + if not self.has_swarm_lock_changed(): + return default + try: + return self.client.get_unlock_key() or default + except APIError: + return default + + def has_swarm_lock_changed(self): + return self.parameters.autolock_managers and ( + self.created or self.differences.has_difference_for('autolock_managers') + ) + def init_swarm(self): if not self.force and self.client.check_if_swarm_manager(): self.__update_swarm() @@ -479,11 +508,16 @@ class SwarmManager(DockerBaseClass): if not self.client.check_if_swarm_manager(): if not self.check_mode: self.client.fail("Swarm not created or other error!") + + self.created = True self.inspect_swarm() self.results['actions'].append("New Swarm cluster created: %s" % (self.swarm_info.get('ID'))) self.differences.add('state', parameter='present', active='absent') self.results['changed'] = True - self.results['swarm_facts'] = {u'JoinTokens': self.swarm_info.get('JoinTokens')} + self.results['swarm_facts'] = { + 'JoinTokens': self.swarm_info.get('JoinTokens'), + 'UnlockKey': self.swarm_info.get('UnlockKey') + } def __update_swarm(self): try: diff --git a/lib/ansible/modules/cloud/docker/docker_swarm_info.py b/lib/ansible/modules/cloud/docker/docker_swarm_info.py index 0302aff8faf..0f260b2871f 100644 --- a/lib/ansible/modules/cloud/docker/docker_swarm_info.py +++ b/lib/ansible/modules/cloud/docker/docker_swarm_info.py @@ -29,7 +29,7 @@ description: version_added: "2.8" author: - - Piotr Wojciechowski (@wojciechowskipiotr) + - Piotr Wojciechowski (@WojciechowskiPiotr) options: nodes: @@ -68,6 +68,11 @@ options: - See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/service_ps/#filtering) for more information on possible filters. type: dict + unlock_key: + description: + - Whether to retrieve the swarm unlock key. + type: bool + default: no verbose_output: description: - When set to C(yes) and I(nodes), I(services) or I(tasks) is set to C(yes) @@ -121,6 +126,15 @@ EXAMPLES = ''' - debug: var: result.swarm_facts + +- name: Get the swarm unlock key + docker_swarm_info: + unlock_key: yes + register: result + +- debug: + var: result.swarm_unlock_key + ''' RETURN = ''' @@ -143,13 +157,17 @@ docker_swarm_manager: - Only if this one is C(true), the module will not fail. returned: both on success and on error type: bool - swarm_facts: description: - Facts representing the basic state of the docker Swarm cluster. - Contains tokens to connect to the Swarm returned: always type: dict +swarm_unlock_key: + description: + - Contains the key needed to unlock the swarm. + returned: When I(unlock_key) is C(true). + type: str nodes: description: - List of dict objects containing the basic information about each volume. @@ -208,6 +226,8 @@ class DockerSwarmManager(DockerBaseClass): filter_name = docker_object + "_filters" filters = clean_dict_booleans_for_docker_api(client.module.params.get(filter_name)) self.results[returned_name] = self.get_docker_items_list(docker_object, filters) + if self.client.module.params['unlock_key']: + self.results['swarm_unlock_key'] = self.get_docker_swarm_unlock_key() def get_docker_swarm_facts(self): try: @@ -305,6 +325,10 @@ class DockerSwarmManager(DockerBaseClass): return object_essentials + def get_docker_swarm_unlock_key(self): + unlock_key = self.client.get_unlock_key() or {} + return unlock_key.get('UnlockKey') or None + def main(): argument_spec = dict( @@ -314,14 +338,19 @@ def main(): tasks_filters=dict(type='dict'), services=dict(type='bool', default=False), services_filters=dict(type='dict'), + unlock_key=dict(type='bool', default=False), verbose_output=dict(type='bool', default=False), ) + option_minimal_versions = dict( + unlock_key=dict(docker_py_version='2.7.0', docker_api_version='1.25'), + ) client = AnsibleDockerSwarmClient( argument_spec=argument_spec, supports_check_mode=True, min_docker_version='1.10.0', min_docker_api_version='1.24', + option_minimal_versions=option_minimal_versions, fail_results=dict( can_talk_to_docker=False, docker_swarm_active=False, diff --git a/test/integration/targets/docker_swarm/tasks/main.yml b/test/integration/targets/docker_swarm/tasks/main.yml index 5bddee1e143..867bbbd1ee5 100644 --- a/test/integration/targets/docker_swarm/tasks/main.yml +++ b/test/integration/targets/docker_swarm/tasks/main.yml @@ -13,11 +13,16 @@ diff: no ignore_errors: yes + - name: Kill docker daemon + command: systemctl kill -s 9 docker + become: yes + - name: Restart docker daemon service: name: docker state: restarted become: yes + - name: Wait for docker daemon to be fully restarted command: docker ps diff --git a/test/integration/targets/docker_swarm/tasks/tests/options.yml b/test/integration/targets/docker_swarm/tasks/tests/options.yml index 0cb19b11c8c..85f8223ddbc 100644 --- a/test/integration/targets/docker_swarm/tasks/tests/options.yml +++ b/test/integration/targets/docker_swarm/tasks/tests/options.yml @@ -61,6 +61,15 @@ register: output_6 ignore_errors: yes +- name: autolock_managers (force new swarm) + docker_swarm: + state: present + force: yes + autolock_managers: yes + diff: yes + register: output_7 + ignore_errors: yes + - name: assert autolock_managers changes assert: that: @@ -89,6 +98,16 @@ - 'output_6.diff.before is defined' - 'output_6.diff.after is defined' when: docker_py_version is version('2.6.0', '>=') + +- name: assert UnlockKey in swarm_facts + assert: + that: + - 'output_2.swarm_facts.UnlockKey' + - 'output_3.swarm_facts.UnlockKey is none' + - 'output_6.swarm_facts.UnlockKey is none' + - 'output_7.swarm_facts.UnlockKey' + when: docker_py_version is version('2.7.0', '>=') + - assert: that: - output_1 is failed diff --git a/test/integration/targets/docker_swarm_info/tasks/test_swarm_info.yml b/test/integration/targets/docker_swarm_info/tasks/test_swarm_info.yml index 8710f790801..e65d2dfcd02 100644 --- a/test/integration/targets/docker_swarm_info/tasks/test_swarm_info.yml +++ b/test/integration/targets/docker_swarm_info/tasks/test_swarm_info.yml @@ -18,6 +18,7 @@ - 'output.can_talk_to_docker == true' - 'output.docker_swarm_active == false' - 'output.docker_swarm_manager == false' + - 'output.swarm_unlock_key is not defined' - name: Create a Swarm cluster docker_swarm: @@ -45,6 +46,7 @@ - 'output.can_talk_to_docker == true' - 'output.docker_swarm_active == true' - 'output.docker_swarm_manager == true' + - 'output.swarm_unlock_key is not defined' - name: Try to get docker_swarm_info and list of nodes when docker is running in swarm mode and as manager docker_swarm_info: @@ -61,6 +63,7 @@ - 'output.can_talk_to_docker == true' - 'output.docker_swarm_active == true' - 'output.docker_swarm_manager == true' + - 'output.swarm_unlock_key is not defined' - name: Get local docker node name set_fact: @@ -84,6 +87,7 @@ - 'output.can_talk_to_docker == true' - 'output.docker_swarm_active == true' - 'output.docker_swarm_manager == true' + - 'output.swarm_unlock_key is not defined' - name: Try to get docker_swarm_info and list of nodes with filters providing existing node name docker_swarm_info: @@ -102,6 +106,7 @@ - 'output.can_talk_to_docker == true' - 'output.docker_swarm_active == true' - 'output.docker_swarm_manager == true' + - 'output.swarm_unlock_key is not defined' - name: Create random name set_fact: @@ -124,6 +129,40 @@ - 'output.can_talk_to_docker == true' - 'output.docker_swarm_active == true' - 'output.docker_swarm_manager == true' + - 'output.swarm_unlock_key is not defined' + + - name: Try to get docker_swarm_info and swarm_unlock_key on non a unlocked swarm + docker_swarm_info: + unlock_key: yes + register: output + + - name: assert reading swarm facts and non existing swarm unlock key + assert: + that: + - 'output.swarm_unlock_key is none' + - 'output.can_talk_to_docker == true' + - 'output.docker_swarm_active == true' + - 'output.docker_swarm_manager == true' + + - name: Update swarm cluster to be locked + docker_swarm: + state: present + autolock_managers: true + register: autolock_managers_update_output + + - name: Try to get docker_swarm_info and swarm_unlock_key + docker_swarm_info: + unlock_key: yes + register: output + + - name: assert reading swarm facts and swarm unlock key + assert: + that: + - 'output.swarm_unlock_key is string' + - 'output.swarm_unlock_key == autolock_managers_update_output.swarm_facts.UnlockKey' + - 'output.can_talk_to_docker == true' + - 'output.docker_swarm_active == true' + - 'output.docker_swarm_manager == true' always: - name: Cleanup