mirror of
https://github.com/ansible-collections/community.docker.git
synced 2025-12-15 19:42:06 +00:00
* Add typing to Docker Stack modules. Clean modules up. * Add typing to Docker Swarm modules. * Add typing to unit tests. * Add more typing. * Add ignore.txt entries.
413 lines
14 KiB
Python
413 lines
14 KiB
Python
#!/usr/bin/python
|
|
#
|
|
# Copyright (c) 2019 Piotr Wojciechowski <piotr@it-playground.pl>
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
DOCUMENTATION = r"""
|
|
module: docker_swarm_info
|
|
|
|
short_description: Retrieves facts about Docker Swarm cluster
|
|
|
|
description:
|
|
- Retrieves facts about a Docker Swarm.
|
|
- Returns lists of swarm objects names for the services - nodes, services, tasks.
|
|
- The output differs depending on API version available on docker host.
|
|
- Must be run on Swarm Manager node; otherwise module fails with error message. It does return boolean flags in on both
|
|
error and success which indicate whether the docker daemon can be communicated with, whether it is in Swarm mode, and
|
|
whether it is a Swarm Manager node.
|
|
author:
|
|
- Piotr Wojciechowski (@WojciechowskiPiotr)
|
|
|
|
extends_documentation_fragment:
|
|
- community.docker._docker
|
|
- community.docker._docker.docker_py_2_documentation
|
|
- community.docker._attributes
|
|
- community.docker._attributes.actiongroup_docker
|
|
- community.docker._attributes.info_module
|
|
- community.docker._attributes.idempotent_not_modify_state
|
|
|
|
options:
|
|
nodes:
|
|
description:
|
|
- Whether to list swarm nodes.
|
|
type: bool
|
|
default: false
|
|
nodes_filters:
|
|
description:
|
|
- A dictionary of filter values used for selecting nodes to list.
|
|
- 'For example, C(name: mynode).'
|
|
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/node_ls/#filtering) for more information
|
|
on possible filters.
|
|
type: dict
|
|
services:
|
|
description:
|
|
- Whether to list swarm services.
|
|
type: bool
|
|
default: false
|
|
services_filters:
|
|
description:
|
|
- A dictionary of filter values used for selecting services to list.
|
|
- 'For example, C(name: myservice).'
|
|
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/service_ls/#filtering) for more
|
|
information on possible filters.
|
|
type: dict
|
|
tasks:
|
|
description:
|
|
- Whether to list containers.
|
|
type: bool
|
|
default: false
|
|
tasks_filters:
|
|
description:
|
|
- A dictionary of filter values used for selecting tasks to list.
|
|
- 'For example, C(node: mynode-1).'
|
|
- 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: false
|
|
verbose_output:
|
|
description:
|
|
- When set to V(true) and O(nodes), O(services), or O(tasks) is set to V(true), then the module output will contain
|
|
verbose information about objects matching the full output of API method.
|
|
- For details see the documentation of your version of Docker API at U(https://docs.docker.com/engine/api/).
|
|
- The verbose output in this module contains only subset of information returned by this info module for each type of
|
|
the objects.
|
|
type: bool
|
|
default: false
|
|
|
|
requirements:
|
|
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 2.0.0"
|
|
- "Docker API >= 1.25"
|
|
"""
|
|
|
|
EXAMPLES = r"""
|
|
---
|
|
- name: Get info on Docker Swarm
|
|
community.docker.docker_swarm_info:
|
|
ignore_errors: true
|
|
register: result
|
|
|
|
- name: Inform about basic flags
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
Was able to talk to docker daemon: {{ result.can_talk_to_docker }}
|
|
Docker in Swarm mode: {{ result.docker_swarm_active }}
|
|
This is a Manager node: {{ result.docker_swarm_manager }}
|
|
|
|
- name: Get info on Docker Swarm and list of registered nodes
|
|
community.docker.docker_swarm_info:
|
|
nodes: true
|
|
register: result
|
|
|
|
- name: Get info on Docker Swarm and extended list of registered nodes
|
|
community.docker.docker_swarm_info:
|
|
nodes: true
|
|
verbose_output: true
|
|
register: result
|
|
|
|
- name: Get info on Docker Swarm and filtered list of registered nodes
|
|
community.docker.docker_swarm_info:
|
|
nodes: true
|
|
nodes_filters:
|
|
name: mynode
|
|
register: result
|
|
|
|
- name: Show swarm facts
|
|
ansible.builtin.debug:
|
|
var: result.swarm_facts
|
|
|
|
- name: Get the swarm unlock key
|
|
community.docker.docker_swarm_info:
|
|
unlock_key: true
|
|
register: result
|
|
|
|
- name: Print swarm unlock key
|
|
ansible.builtin.debug:
|
|
var: result.swarm_unlock_key
|
|
"""
|
|
|
|
RETURN = r"""
|
|
can_talk_to_docker:
|
|
description:
|
|
- Will be V(true) if the module can talk to the docker daemon.
|
|
returned: both on success and on error
|
|
type: bool
|
|
docker_swarm_active:
|
|
description:
|
|
- Will be V(true) if the module can talk to the docker daemon, and the docker daemon is in Swarm mode.
|
|
returned: both on success and on error
|
|
type: bool
|
|
docker_swarm_manager:
|
|
description:
|
|
- Will be V(true) if the module can talk to the docker daemon, the docker daemon is in Swarm mode, and the current node
|
|
is a manager node.
|
|
- Only if this one is V(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 O(unlock_key=true).
|
|
type: str
|
|
nodes:
|
|
description:
|
|
- List of dict objects containing the basic information about each volume. Keys matches the C(docker node ls) output unless
|
|
O(verbose_output=true). See description for O(verbose_output).
|
|
returned: When O(nodes=true)
|
|
type: list
|
|
elements: dict
|
|
services:
|
|
description:
|
|
- List of dict objects containing the basic information about each volume. Keys matches the C(docker service ls) output
|
|
unless O(verbose_output=true). See description for O(verbose_output).
|
|
returned: When O(services=true)
|
|
type: list
|
|
elements: dict
|
|
tasks:
|
|
description:
|
|
- List of dict objects containing the basic information about each volume. Keys matches the C(docker service ps) output
|
|
unless O(verbose_output=true). See description for O(verbose_output).
|
|
returned: When O(tasks=true)
|
|
type: list
|
|
elements: dict
|
|
"""
|
|
|
|
import traceback
|
|
import typing as t
|
|
|
|
|
|
try:
|
|
from docker.errors import APIError, DockerException
|
|
except ImportError:
|
|
# missing Docker SDK for Python handled in ansible.module_utils.docker_common
|
|
pass
|
|
|
|
from ansible_collections.community.docker.plugins.module_utils._common import (
|
|
RequestException,
|
|
)
|
|
from ansible_collections.community.docker.plugins.module_utils._swarm import (
|
|
AnsibleDockerSwarmClient,
|
|
)
|
|
from ansible_collections.community.docker.plugins.module_utils._util import (
|
|
DockerBaseClass,
|
|
clean_dict_booleans_for_docker_api,
|
|
)
|
|
|
|
|
|
class DockerSwarmManager(DockerBaseClass):
|
|
def __init__(
|
|
self, client: AnsibleDockerSwarmClient, results: dict[str, t.Any]
|
|
) -> None:
|
|
super().__init__()
|
|
|
|
self.client = client
|
|
self.results = results
|
|
self.verbose_output = self.client.module.params["verbose_output"]
|
|
|
|
listed_objects: list[t.Literal["nodes", "tasks", "services"]] = [
|
|
"tasks",
|
|
"services",
|
|
"nodes",
|
|
]
|
|
|
|
self.client.fail_task_if_not_swarm_manager()
|
|
|
|
self.results["swarm_facts"] = self.get_docker_swarm_facts()
|
|
|
|
for docker_object in listed_objects:
|
|
if self.client.module.params[docker_object]:
|
|
returned_name = docker_object
|
|
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) -> dict[str, t.Any]:
|
|
try:
|
|
return self.client.inspect_swarm()
|
|
except APIError as exc:
|
|
self.client.fail(f"Error inspecting docker swarm: {exc}")
|
|
|
|
def get_docker_items_list(
|
|
self,
|
|
docker_object: t.Literal["nodes", "tasks", "services"],
|
|
filters: dict[str, str],
|
|
) -> list[dict[str, t.Any]]:
|
|
items_list: list[dict[str, t.Any]] = []
|
|
|
|
try:
|
|
if docker_object == "nodes":
|
|
items = self.client.nodes(filters=filters)
|
|
elif docker_object == "tasks":
|
|
items = self.client.tasks(filters=filters)
|
|
elif docker_object == "services":
|
|
items = self.client.services(filters=filters)
|
|
else:
|
|
raise ValueError(f"Invalid docker_object {docker_object}")
|
|
except APIError as exc:
|
|
self.client.fail(
|
|
f"Error inspecting docker swarm for object '{docker_object}': {exc}"
|
|
)
|
|
|
|
if self.verbose_output:
|
|
return items
|
|
|
|
for item in items:
|
|
item_record = {}
|
|
|
|
if docker_object == "nodes":
|
|
item_record = self.get_essential_facts_nodes(item)
|
|
elif docker_object == "tasks":
|
|
item_record = self.get_essential_facts_tasks(item)
|
|
elif docker_object == "services":
|
|
item_record = self.get_essential_facts_services(item)
|
|
if item_record.get("Mode") == "Global":
|
|
item_record["Replicas"] = len(items)
|
|
items_list.append(item_record)
|
|
|
|
return items_list
|
|
|
|
@staticmethod
|
|
def get_essential_facts_nodes(item: dict[str, t.Any]) -> dict[str, t.Any]:
|
|
object_essentials = {}
|
|
|
|
object_essentials["ID"] = item.get("ID")
|
|
object_essentials["Hostname"] = item["Description"]["Hostname"]
|
|
object_essentials["Status"] = item["Status"]["State"]
|
|
object_essentials["Availability"] = item["Spec"]["Availability"]
|
|
if "ManagerStatus" in item:
|
|
object_essentials["ManagerStatus"] = item["ManagerStatus"]["Reachability"]
|
|
if (
|
|
"Leader" in item["ManagerStatus"]
|
|
and item["ManagerStatus"]["Leader"] is True
|
|
):
|
|
object_essentials["ManagerStatus"] = "Leader"
|
|
else:
|
|
object_essentials["ManagerStatus"] = None
|
|
object_essentials["EngineVersion"] = item["Description"]["Engine"][
|
|
"EngineVersion"
|
|
]
|
|
|
|
return object_essentials
|
|
|
|
def get_essential_facts_tasks(self, item: dict[str, t.Any]) -> dict[str, t.Any]:
|
|
object_essentials = {}
|
|
|
|
object_essentials["ID"] = item["ID"]
|
|
# Returning container ID to not trigger another connection to host
|
|
# Container ID is sufficient to get extended info in other tasks
|
|
object_essentials["ContainerID"] = item["Status"]["ContainerStatus"][
|
|
"ContainerID"
|
|
]
|
|
object_essentials["Image"] = item["Spec"]["ContainerSpec"]["Image"]
|
|
object_essentials["Node"] = self.client.get_node_name_by_id(item["NodeID"])
|
|
object_essentials["DesiredState"] = item["DesiredState"]
|
|
object_essentials["CurrentState"] = item["Status"]["State"]
|
|
if "Err" in item["Status"]:
|
|
object_essentials["Error"] = item["Status"]["Err"]
|
|
else:
|
|
object_essentials["Error"] = None
|
|
|
|
return object_essentials
|
|
|
|
@staticmethod
|
|
def get_essential_facts_services(item: dict[str, t.Any]) -> dict[str, t.Any]:
|
|
object_essentials = {}
|
|
|
|
object_essentials["ID"] = item["ID"]
|
|
object_essentials["Name"] = item["Spec"]["Name"]
|
|
if "Replicated" in item["Spec"]["Mode"]:
|
|
object_essentials["Mode"] = "Replicated"
|
|
object_essentials["Replicas"] = item["Spec"]["Mode"]["Replicated"][
|
|
"Replicas"
|
|
]
|
|
elif "Global" in item["Spec"]["Mode"]:
|
|
object_essentials["Mode"] = "Global"
|
|
# Number of replicas have to be updated in calling method or may be left as None
|
|
object_essentials["Replicas"] = None
|
|
object_essentials["Image"] = item["Spec"]["TaskTemplate"]["ContainerSpec"][
|
|
"Image"
|
|
]
|
|
if item["Spec"].get("EndpointSpec") and "Ports" in item["Spec"]["EndpointSpec"]:
|
|
object_essentials["Ports"] = item["Spec"]["EndpointSpec"]["Ports"]
|
|
else:
|
|
object_essentials["Ports"] = []
|
|
|
|
return object_essentials
|
|
|
|
def get_docker_swarm_unlock_key(self) -> str | None:
|
|
unlock_key = self.client.get_unlock_key() or {}
|
|
return unlock_key.get("UnlockKey") or None
|
|
|
|
|
|
def main() -> None:
|
|
argument_spec = {
|
|
"nodes": {"type": "bool", "default": False},
|
|
"nodes_filters": {"type": "dict"},
|
|
"tasks": {"type": "bool", "default": False},
|
|
"tasks_filters": {"type": "dict"},
|
|
"services": {"type": "bool", "default": False},
|
|
"services_filters": {"type": "dict"},
|
|
"unlock_key": {"type": "bool", "default": False},
|
|
"verbose_output": {"type": "bool", "default": False},
|
|
}
|
|
option_minimal_versions = {
|
|
"unlock_key": {"docker_py_version": "2.7.0"},
|
|
}
|
|
|
|
client = AnsibleDockerSwarmClient(
|
|
argument_spec=argument_spec,
|
|
supports_check_mode=True,
|
|
min_docker_version="2.0.0",
|
|
option_minimal_versions=option_minimal_versions,
|
|
fail_results={
|
|
"can_talk_to_docker": False,
|
|
"docker_swarm_active": False,
|
|
"docker_swarm_manager": False,
|
|
},
|
|
)
|
|
client.fail_results["can_talk_to_docker"] = True
|
|
client.fail_results["docker_swarm_active"] = client.check_if_swarm_node()
|
|
client.fail_results["docker_swarm_manager"] = client.check_if_swarm_manager()
|
|
|
|
try:
|
|
results = {
|
|
"changed": False,
|
|
}
|
|
|
|
DockerSwarmManager(client, results)
|
|
results.update(client.fail_results)
|
|
client.module.exit_json(**results)
|
|
except DockerException as e:
|
|
client.fail(
|
|
f"An unexpected Docker error occurred: {e}",
|
|
exception=traceback.format_exc(),
|
|
)
|
|
except RequestException as e:
|
|
client.fail(
|
|
f"An unexpected requests error occurred when Docker SDK for Python tried to talk to the docker daemon: {e}",
|
|
exception=traceback.format_exc(),
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|