Skip to content

Commit 3f65596

Browse files
Dan Lavudanlavu
authored andcommitted
extending getent to support hosts, networks and services
1 parent d364328 commit 3f65596

File tree

1 file changed

+270
-1
lines changed

1 file changed

+270
-1
lines changed

sssd_test_framework/utils/tools.py

Lines changed: 270 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from typing import Any
5+
from typing import Any, Sequence
66

77
import jc
88
from pytest_mh import MultihostHost, MultihostUtility
@@ -512,6 +512,236 @@ def FromOutput(cls, stdout: str) -> NetgroupEntry:
512512
return cls.FromDict({"name": result[0], "members": members})
513513

514514

515+
class HostsEntry(object):
516+
"""
517+
Result of ``getent hosts``
518+
519+
.. code-block:: python
520+
:caption: Example usage
521+
522+
ldap.hosts("host0").add(ip_address="192.168.1.1", aliases=[])
523+
client.sssd.common.resolver(ldap)
524+
client.sssd.start()
525+
526+
result = client.tools.getent.hosts("host0", service="sss")
527+
if result is not None:
528+
assert "host0" == result.name, f"Host 'host0' was not found!"
529+
assert "192.168.1.1" in result.ip, f"IP addresses '192.168.1.1' was not found!"
530+
"""
531+
532+
def __init__(self, name: str | None, ip: list[str] | None, aliases: list[str] | None) -> None:
533+
534+
self.name: str | None = name
535+
"""Hostname."""
536+
537+
self.ip: list[str] | None = ip
538+
"""IP address."""
539+
540+
self.aliases: list[str] | None = aliases
541+
"""Host aliases."""
542+
543+
def __str__(self) -> str:
544+
return f"({self.ip}: {self.name}, {self.aliases})"
545+
546+
def __repr__(self) -> str:
547+
return str(self)
548+
549+
@classmethod
550+
def FromDict(cls, d: dict[str, Any]) -> HostsEntry:
551+
if isinstance(d, dict):
552+
ip = d.get("ip", None)
553+
name = d.get("name", None)
554+
aliases = d.get("aliases", None)
555+
556+
if name is not None and not isinstance(name, str):
557+
raise ValueError("name is not an instance of str")
558+
559+
if ip is not None and not isinstance(ip, list):
560+
raise ValueError("ip is not an instance of list")
561+
562+
if aliases is not None and not isinstance(aliases, list):
563+
raise ValueError("aliases is not an instance of list")
564+
565+
return cls(name=name, ip=ip, aliases=aliases)
566+
567+
@classmethod
568+
def FromList(cls, d: list[dict[str, Any]]) -> HostsEntry:
569+
name: str | None = None
570+
ip: list[str] = []
571+
aliases: list[str] = []
572+
573+
for i in d:
574+
if isinstance(i, dict):
575+
ip_value = i.get("ip")
576+
if isinstance(ip_value, str) and ip_value:
577+
ip.append(ip_value)
578+
elif isinstance(ip_value, list):
579+
ip.extend([v for v in ip_value if isinstance(v, str)])
580+
581+
hostname = i.get("hostname")
582+
if isinstance(hostname, list) and hostname:
583+
if name is None and isinstance(hostname[0], str):
584+
name = hostname[0]
585+
if len(hostname) > 1:
586+
for host in hostname[1:]:
587+
if isinstance(host, str) and host not in aliases:
588+
aliases.append(host)
589+
590+
return cls.FromDict({"name": name, "ip": ip if ip else None, "aliases": aliases if aliases else None})
591+
592+
@classmethod
593+
def FromOutput(cls, stdout: str) -> HostsEntry:
594+
result = jc.parse("hosts", stdout)
595+
596+
if not isinstance(result, list):
597+
raise TypeError(f"Unexpected type: {type(result)}, expecting list")
598+
599+
if not result:
600+
raise ValueError("No hosts data found in output")
601+
602+
return cls.FromList(result)
603+
604+
605+
class NetworksEntry(object):
606+
"""
607+
Result of ``getent networks``
608+
609+
If networks does not exist or does not have any supplementary networks then ``self.networks`` is empty.
610+
611+
.. code-block:: python
612+
:caption: Example usage
613+
614+
ldap.networks("net0").add(ip_address ="192.168.1.1", aliases = [])
615+
client.sssd.common.resolver(ldap)
616+
client.sssd.start()
617+
618+
result = client.tools.getent.networks("net0", service="sss")
619+
if result is not None:
620+
assert "net0" in result.name, f"Network 'net0' was not found!"
621+
"""
622+
623+
def __init__(self, name: str | None | None, ip: str | None, aliases: Sequence[str] | None) -> None:
624+
625+
self.name: str | None = name
626+
"""Network name. """
627+
628+
self.ip: str | None = ip
629+
"""Exact network name which ``network`` was called."""
630+
631+
self.aliases: Sequence[str] | None = aliases
632+
"""Network aliases."""
633+
634+
def __str__(self) -> str:
635+
return f"({self.ip}: {self.name} {self.aliases})"
636+
637+
def __repr__(self) -> str:
638+
return str(self)
639+
640+
@classmethod
641+
def FromDict(cls, d: dict[str, Sequence[str]]) -> NetworksEntry:
642+
if isinstance(d, dict):
643+
ip = d.get("ip", None)
644+
name = d.get("name", None)
645+
aliases = d.get("aliases", None)
646+
647+
if ip is not None and not isinstance(ip, str):
648+
raise ValueError("ip is not an instance of str")
649+
650+
if name is not None and not isinstance(name, str):
651+
raise ValueError("hostname is not an instance of str")
652+
653+
return cls(ip=ip, name=name, aliases=aliases)
654+
655+
@classmethod
656+
def FromOutput(cls, stdout: str) -> NetworksEntry:
657+
parts = [part for part in stdout.split() if part]
658+
if len(parts) < 2:
659+
raise ValueError(f"Two few values {len(parts)}: {parts}")
660+
661+
aliases = []
662+
if len(parts) > 2:
663+
for host in parts[2:]:
664+
if isinstance(host, str) and host not in aliases:
665+
aliases.append(host)
666+
result = {"name": parts[0].strip(), "ip": parts[1].strip(), "aliases": aliases}
667+
668+
return cls.FromDict(result)
669+
670+
671+
class ServicesEntry(object):
672+
"""
673+
Result of ``getent services``
674+
675+
If services does not exist or does not have any supplementary services then ``self.services`` is empty.
676+
677+
.. code-block:: python
678+
:caption: Example usage
679+
680+
ldap.services("service0").add(port=12345, protocol="tcp", aliases = [])
681+
client.sssd.common.resolver(ldap)
682+
client.sssd.start()
683+
684+
result = client.tools.getent.services("service0", service="sss")
685+
if result is not None:
686+
assert "service0" == result.name, f"Service 'service0' was not found!"
687+
"""
688+
689+
def __init__(self, name: str | None, protocol: str | None = None, port: int | None = None) -> None:
690+
691+
self.name: str | None = name
692+
"""Service name. """
693+
694+
self.protocol: str | None = protocol
695+
"""Protocol."""
696+
697+
self.port: int | None = port
698+
"""Port."""
699+
700+
def __str__(self) -> str:
701+
return f"({self.name} {self.port}/{self.protocol})"
702+
703+
def __repr__(self) -> str:
704+
return str(self)
705+
706+
@classmethod
707+
def FromDict(cls, d: dict[str, Any | None]) -> ServicesEntry:
708+
if isinstance(d, dict):
709+
name = d.get("name", None)
710+
protocol = d.get("protocol", None)
711+
port = d.get("port", None)
712+
713+
if name is not None and not isinstance(name, str):
714+
raise ValueError("name is not an instance of str")
715+
716+
if protocol is not None and not isinstance(protocol, str):
717+
raise ValueError("protocol is not an instance of str")
718+
719+
if port is not None and not isinstance(port, int):
720+
raise ValueError("port is not an instance of int")
721+
722+
return cls(name=name, protocol=protocol, port=port)
723+
724+
@classmethod
725+
def FromOutput(cls, stdout: str) -> ServicesEntry:
726+
name: str = ""
727+
port: int = 0
728+
protocol: str = ""
729+
730+
parts: list[str] = [part for part in stdout.split() if part]
731+
if len(parts) < 2:
732+
raise ValueError(f"Two few values {len(parts)}: {parts}")
733+
734+
name = parts[0].strip()
735+
736+
if "/" in parts[1]:
737+
port = int(parts[1].split("/")[0].strip())
738+
protocol = parts[1].split("/")[1].strip()
739+
740+
result = {"name": name, "port": port, "protocol": protocol}
741+
742+
return cls.FromDict(result)
743+
744+
515745
class LinuxToolsUtils(MultihostUtility[MultihostHost]):
516746
"""
517747
Run various standard commands on remote host.
@@ -749,6 +979,45 @@ def netgroup(self, name: str, *, service: str | None = None) -> NetgroupEntry |
749979
"""
750980
return self.__exec(NetgroupEntry, "netgroup", name, service)
751981

982+
def hosts(self, name: str, *, service: str | None = None) -> HostsEntry:
983+
"""
984+
Call ``getent hosts $name``
985+
986+
:param name: Hostname.
987+
:type name: str
988+
:param service: Service used, defaults to None
989+
:type service: str | None
990+
:return: Hosts data, None if not found
991+
:rtype: HostsEntry | None
992+
"""
993+
return self.__exec(HostsEntry, "hosts", name, service)
994+
995+
def networks(self, name: str, *, service: str | None = None) -> NetworksEntry:
996+
"""
997+
Call ``getent networks $name``
998+
999+
:param name: Network.
1000+
:type name: str
1001+
:param service: Service used, defaults to None
1002+
:type service: str | None
1003+
:return: Network data, None if not found
1004+
:rtype: NetworksEntry | None
1005+
"""
1006+
return self.__exec(NetworksEntry, "networks", name, service)
1007+
1008+
def services(self, name: str, *, service: str | None = None) -> ServicesEntry:
1009+
"""
1010+
Call ``getent services $name``
1011+
1012+
:param name: Service.
1013+
:type name: str
1014+
:param service: Service used, defaults to None
1015+
:type service: str | None
1016+
:return: Service data, None if not found
1017+
:rtype: ServicesEntry | None
1018+
"""
1019+
return self.__exec(ServicesEntry, "services", name, service)
1020+
7521021
def __exec(self, cls, cmd: str, name: str | int, service: str | None = None) -> Any:
7531022
args = []
7541023
if service is not None:

0 commit comments

Comments
 (0)