Skip to content

Commit 61b559c

Browse files
a-gabidullinАлександр Габидуллин
andauthored
add sssd_info module (ansible-collections#11120)
* add sssd_info module * fix f-stings and remove support python2 * fix imports custom lib * fix whitespace and add missing_required_lib * fix str and add version * try add mock test * fix module and mock tests check * fix required in main module * fix spaces * fix linters * add final newline * fix version of module * fix description and error handling * swap literal to dict * fix str * remove comment in methods * remove _get in methods * fix name method in test * add botmeta * fix description of server_type * fix name of maintainer * remove choices * fix author * fix type hint * fix result * fix spaces * fix choices and empty returns * fix mypy test result * fix result * run andebox yaml-doc * remake simple try/exc for result * fix tests * add any type for testing mypy * ruff formated * fix docs * remove unittest.main * rename acc on git for official name --------- Co-authored-by: Александр Габидуллин <agabidullin@astralinux.ru>
1 parent 02b1859 commit 61b559c

3 files changed

Lines changed: 444 additions & 0 deletions

File tree

.github/BOTMETA.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,8 @@ files:
15021502
maintainers: vbotka
15031503
$tests/fqdn_valid.py:
15041504
maintainers: vbotka
1505+
$modules/sssd_info.py:
1506+
maintainers: a-gabidullin
15051507
#########################
15061508
docs/docsite/rst/filter_guide.rst: {}
15071509
docs/docsite/rst/filter_guide_abstract_informations.rst: {}

plugins/modules/sssd_info.py

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
#!/usr/bin/python
2+
# Copyright (c) 2025 Aleksandr Gabidullin <qualittv@gmail.com>
3+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
4+
# SPDX-License-Identifier: GPL-3.0-or-later
5+
6+
from __future__ import annotations
7+
8+
9+
DOCUMENTATION = r"""
10+
module: sssd_info
11+
version_added: 12.2.0
12+
short_description: Check SSSD domain status using D-Bus
13+
description:
14+
- Check the online status of SSSD domains, list domains, and retrieve active servers using D-Bus.
15+
author: "Aleksandr Gabidullin (@a-gabidullin)"
16+
requirements:
17+
- dbus
18+
attributes:
19+
check_mode:
20+
support: full
21+
diff_mode:
22+
support: none
23+
platform:
24+
platforms: posix
25+
description: This action requires a system with D-Bus and SSSD running.
26+
support: full
27+
options:
28+
action:
29+
description:
30+
- The action to perform.
31+
type: str
32+
required: true
33+
choices:
34+
domain_status: Check if domain is online.
35+
domain_list: List all configured domains.
36+
active_servers: Get active servers for domain.
37+
list_servers: List all servers for domain.
38+
domain:
39+
description:
40+
- Domain name to check.
41+
- Required unless O(action=domain_list).
42+
- When O(action=domain_list), this parameter is ignored and the module returns a list of all configured domains.
43+
type: str
44+
server_type:
45+
description:
46+
- Required parameter when O(action=active_servers) and O(action=list_servers).
47+
- Optional and ignored for all other actions.
48+
- At this point, the module supports ONLY the types C(IPA) for FreeIPA servers and C(AD).
49+
type: str
50+
choices: ['IPA', 'AD']
51+
extends_documentation_fragment:
52+
- community.general.attributes
53+
"""
54+
55+
EXAMPLES = r"""
56+
- name: Check SSSD domain status
57+
community.general.sssd_info:
58+
action: domain_status
59+
domain: example.com
60+
register: sssd_status_result
61+
62+
- name: Get domain list
63+
community.general.sssd_info:
64+
action: domain_list
65+
register: domain_list_result
66+
67+
- name: Get active IPA servers for a domain
68+
community.general.sssd_info:
69+
action: active_servers
70+
domain: example.com
71+
server_type: IPA
72+
register: active_servers_result
73+
74+
- name: List servers for a domain
75+
community.general.sssd_info:
76+
action: list_servers
77+
domain: example.com
78+
server_type: AD
79+
register: list_servers_result
80+
"""
81+
82+
RETURN = r"""
83+
online:
84+
description: The online status of the SSSD domain.
85+
type: str
86+
returned: when O(action=domain_status)
87+
sample: online
88+
domain_list:
89+
description: List of SSSD domains.
90+
type: list
91+
elements: str
92+
returned: when O(action=domain_list)
93+
sample: ["ipa.domain", "winad.test"]
94+
servers:
95+
description: Active servers for the specified domain and type.
96+
type: dict
97+
returned: when O(action=active_servers)
98+
sample: {"Global Catalog": "server1.winad.test", "Domain Server": "server2.winad.test"}
99+
list_servers:
100+
description: List of servers for the specified domain.
101+
type: list
102+
elements: str
103+
returned: when O(action=list_servers)
104+
sample: ["server1.winad.test", "server2.winad.test"]
105+
"""
106+
107+
108+
from ansible.module_utils.basic import AnsibleModule
109+
from typing import Any
110+
from ansible_collections.community.general.plugins.module_utils import deps
111+
112+
with deps.declare("dbus"):
113+
import dbus
114+
115+
116+
class SSSDHandler:
117+
"""SSSD D-Bus handler"""
118+
119+
BUS_NAME = "org.freedesktop.sssd.infopipe"
120+
DOMAIN_INTERFACE = "org.freedesktop.sssd.infopipe.Domains.Domain"
121+
INFOPIPE_INTERFACE = "org.freedesktop.sssd.infopipe"
122+
123+
def __init__(self) -> None:
124+
"""Initialize SSSD D-Bus connection."""
125+
self.bus = dbus.SystemBus()
126+
self.sssd_obj = self.bus.get_object(self.BUS_NAME, "/org/freedesktop/sssd/infopipe")
127+
self.infopipe_iface = dbus.Interface(self.sssd_obj, dbus_interface=self.INFOPIPE_INTERFACE)
128+
129+
def domain_path(self, domain: str) -> str:
130+
return f"/org/freedesktop/sssd/infopipe/Domains/{domain.replace('.', '_2e')}"
131+
132+
def domain_object(self, domain: str) -> dbus.proxies.ProxyObject:
133+
domain_path = self.domain_path(domain)
134+
try:
135+
return self.bus.get_object(self.BUS_NAME, domain_path)
136+
except dbus.exceptions.DBusException as e:
137+
raise Exception(f"Domain not found: {domain}. Error: {e}") from e
138+
139+
def check_domain_status(self, domain: str) -> str:
140+
domain_obj = self.domain_object(domain)
141+
iface = dbus.Interface(domain_obj, dbus_interface=self.DOMAIN_INTERFACE)
142+
return "online" if iface.IsOnline() else "offline"
143+
144+
def active_servers(self, domain: str, server_type: str) -> dict[str, str]:
145+
"""Get active servers for domain.
146+
147+
Args:
148+
domain: Domain name to get servers for.
149+
server_type: Type of servers ('IPA' or 'AD').
150+
151+
Returns:
152+
Dictionary with server types as keys and server names as values.
153+
"""
154+
domain_obj = self.domain_object(domain)
155+
iface = dbus.Interface(domain_obj, dbus_interface=self.DOMAIN_INTERFACE)
156+
157+
if server_type == "IPA":
158+
server_name = f"{server_type} Server"
159+
return {server_name: iface.ActiveServer(server_type)}
160+
else:
161+
return {
162+
"AD Global Catalog": iface.ActiveServer(f"sd_gc_{domain}"),
163+
"AD Domain Controller": iface.ActiveServer(f"sd_{domain}"),
164+
}
165+
166+
def list_servers(self, domain: str, server_type: str) -> list[str]:
167+
"""List all servers for domain.
168+
169+
Args:
170+
domain: Domain name to list servers for.
171+
server_type: Type of servers ('IPA' or 'AD').
172+
173+
Returns:
174+
List of server names.
175+
"""
176+
domain_obj = self.domain_object(domain)
177+
iface = dbus.Interface(domain_obj, dbus_interface=self.DOMAIN_INTERFACE)
178+
if server_type == "IPA":
179+
return iface.ListServers(server_type)
180+
else:
181+
return iface.ListServers(f"sd_{domain}")
182+
183+
def domain_list(self) -> list[str]:
184+
"""Get list of all domains.
185+
186+
Returns:
187+
List of domain names.
188+
"""
189+
response = self.infopipe_iface.ListDomains()
190+
return [domain.rsplit("/", maxsplit=1)[-1].replace("_2e", ".") for domain in response]
191+
192+
193+
def main() -> None:
194+
"""Main function for the Ansible module."""
195+
module = AnsibleModule(
196+
argument_spec=dict(
197+
action=dict(
198+
type="str",
199+
required=True,
200+
choices=["domain_status", "domain_list", "active_servers", "list_servers"],
201+
),
202+
domain=dict(type="str"),
203+
server_type=dict(type="str", choices=["IPA", "AD"]),
204+
),
205+
supports_check_mode=True,
206+
required_if=[
207+
("action", "domain_status", ["domain"]),
208+
("action", "list_servers", ["domain", "server_type"]),
209+
("action", "active_servers", ["domain", "server_type"]),
210+
],
211+
)
212+
213+
deps.validate(module)
214+
215+
action = module.params["action"]
216+
domain = module.params.get("domain")
217+
server_type = module.params.get("server_type")
218+
219+
sssd = SSSDHandler()
220+
result: dict[str, Any] = {}
221+
222+
try:
223+
if action == "domain_status":
224+
result["online"] = sssd.check_domain_status(domain)
225+
elif action == "domain_list":
226+
result["domain_list"] = sssd.domain_list()
227+
elif action == "active_servers":
228+
result["servers"] = sssd.active_servers(domain, server_type)
229+
elif action == "list_servers":
230+
result["list_servers"] = sssd.list_servers(domain, server_type)
231+
232+
except Exception as e:
233+
module.fail_json(msg=f"Error: {e}")
234+
235+
module.exit_json(**result)
236+
237+
238+
if __name__ == "__main__":
239+
main()

0 commit comments

Comments
 (0)