Skip to content

Commit 11fb5ab

Browse files
phlogistonjohnmergify[bot]
authored andcommitted
grpc: add backend.py for interfacing with samba commands
Create a new backend.py module for working with samba commands within the planned rpc server. This file is not grpc specific but since no other consumers exists it can live in the grpc dir. If we ever add other server types we could move it then. Signed-off-by: John Mulligan <[email protected]>
1 parent 4efce6b commit 11fb5ab

File tree

1 file changed

+161
-0
lines changed

1 file changed

+161
-0
lines changed

sambacc/grpc/backend.py

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#
2+
# sambacc: a samba container configuration tool (and more)
3+
# Copyright (C) 2025 John Mulligan
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>
17+
#
18+
19+
from typing import Any, Union
20+
21+
import dataclasses
22+
import json
23+
import os
24+
import subprocess
25+
26+
from sambacc.typelets import Self
27+
import sambacc.config
28+
import sambacc.samba_cmds
29+
30+
31+
@dataclasses.dataclass
32+
class Versions:
33+
samba_version: str = "foo"
34+
sambacc_version: str = "bar"
35+
container_version: str = "baz"
36+
37+
38+
@dataclasses.dataclass
39+
class Session:
40+
session_id: str
41+
username: str
42+
groupname: str
43+
remote_machine: str
44+
hostname: str
45+
session_dialect: str
46+
uid: int
47+
gid: int
48+
49+
@classmethod
50+
def load(cls, json_object: dict[str, Any]) -> Self:
51+
return cls(
52+
session_id=json_object.get("session_id", ""),
53+
username=json_object.get("username", ""),
54+
groupname=json_object.get("groupname", ""),
55+
remote_machine=json_object.get("remote_machine", ""),
56+
hostname=json_object.get("hostname", ""),
57+
session_dialect=json_object.get("session_dialect", ""),
58+
uid=int(json_object.get("uid", -1)),
59+
gid=int(json_object.get("gid", -1)),
60+
)
61+
62+
63+
@dataclasses.dataclass
64+
class TreeConnection:
65+
tcon_id: str
66+
session_id: str
67+
service_name: str
68+
69+
@classmethod
70+
def load(cls, json_object: dict[str, Any]) -> Self:
71+
return cls(
72+
tcon_id=json_object.get("tcon_id", ""),
73+
session_id=json_object.get("session_id", ""),
74+
service_name=json_object.get("service", ""),
75+
)
76+
77+
78+
@dataclasses.dataclass
79+
class Status:
80+
timestamp: str
81+
version: str
82+
sessions: list[Session]
83+
tcons: list[TreeConnection]
84+
85+
@classmethod
86+
def load(cls, json_object: dict[str, Any]) -> Self:
87+
return cls(
88+
timestamp=json_object.get("timestamp", ""),
89+
version=json_object.get("version", ""),
90+
sessions=[
91+
Session.load(v)
92+
for _, v in json_object.get("sessions", {}).items()
93+
],
94+
tcons=[
95+
TreeConnection.load(v)
96+
for _, v in json_object.get("tcons", {}).items()
97+
],
98+
)
99+
100+
@classmethod
101+
def parse(cls, txt: Union[str, bytes]) -> Self:
102+
return cls.load(json.loads(txt))
103+
104+
105+
class ControlBackend:
106+
def __init__(self, config: sambacc.config.InstanceConfig) -> None:
107+
self._config = config
108+
109+
def _samba_version(self) -> str:
110+
smbd_ver = sambacc.samba_cmds.smbd["--version"]
111+
res = subprocess.run(list(smbd_ver), check=True, capture_output=True)
112+
return res.stdout.decode().strip()
113+
114+
def _sambacc_version(self) -> str:
115+
try:
116+
import sambacc._version
117+
118+
return sambacc._version.version
119+
except ImportError:
120+
return "(unknown)"
121+
122+
def _container_version(self) -> str:
123+
return os.environ.get("SAMBA_CONTAINER_VERSION", "(unknown)")
124+
125+
def get_versions(self) -> Versions:
126+
versions = Versions()
127+
versions.samba_version = self._samba_version()
128+
versions.sambacc_version = self._sambacc_version()
129+
versions.container_version = self._container_version()
130+
return versions
131+
132+
def is_clustered(self) -> bool:
133+
return self._config.with_ctdb
134+
135+
def get_status(self) -> Status:
136+
smbstatus = sambacc.samba_cmds.smbstatus["--json"]
137+
proc = subprocess.Popen(
138+
list(smbstatus),
139+
stdout=subprocess.PIPE,
140+
stderr=subprocess.PIPE,
141+
)
142+
# TODO: the json output of smbstatus is potentially large
143+
# investigate streaming reads instead of fully buffered read
144+
# later
145+
stdout, stderr = proc.communicate()
146+
if proc.returncode != 0:
147+
raise RuntimeError(
148+
f"smbstatus error: {proc.returncode}: {stderr!r}"
149+
)
150+
return Status.parse(stdout)
151+
152+
def close_share(self, share_name: str, denied_users: bool) -> None:
153+
_close = "close-denied-share" if denied_users else "close-share"
154+
cmd = sambacc.samba_cmds.smbcontrol["smbd", _close, share_name]
155+
subprocess.run(list(cmd), check=True)
156+
157+
def kill_client(self, ip_address: str) -> None:
158+
cmd = sambacc.samba_cmds.smbcontrol[
159+
"smbd", "kill-client-ip", ip_address
160+
]
161+
subprocess.run(list(cmd), check=True)

0 commit comments

Comments
 (0)