Skip to content

Commit 9cfd22c

Browse files
phlogistonjohnmergify[bot]
authored andcommitted
commands/remotecontrol: create remote control command
Add a new command line support library for "remote control". Remote control acts as a bridge between local samba command line tools and APIs and remote services that want to manage our instances. Right now the only supported (sub) command is `serve`. The only supported server type is `--grpc`. However, creating a new module with allows for room to grow as well as not interfere with the core sambacc feature set. Signed-off-by: John Mulligan <[email protected]>
1 parent 4e89239 commit 9cfd22c

File tree

3 files changed

+190
-0
lines changed

3 files changed

+190
-0
lines changed

sambacc/commands/remotecontrol/__init__.py

Whitespace-only changes.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#
2+
# sambacc: a samba container configuration tool
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+
import sys
20+
import typing
21+
22+
23+
from .. import skips
24+
from ..cli import Context, Fail, commands
25+
from ..common import (
26+
CommandContext,
27+
enable_logging,
28+
env_to_cli,
29+
global_args,
30+
pre_action,
31+
)
32+
33+
34+
def _default(ctx: Context) -> None:
35+
sys.stdout.write(f"{sys.argv[0]} requires a subcommand, like 'serve'.\n")
36+
sys.exit(1)
37+
38+
39+
def main(args: typing.Optional[typing.Sequence[str]] = None) -> None:
40+
pkg = "sambacc.commands.remotecontrol"
41+
commands.include(".server", package=pkg)
42+
43+
cli = commands.assemble(arg_func=global_args).parse_args(args)
44+
env_to_cli(cli)
45+
enable_logging(cli)
46+
if not cli.identity:
47+
raise Fail("missing container identity")
48+
49+
pre_action(cli)
50+
ctx = CommandContext(cli)
51+
skip = skips.test(ctx)
52+
if skip:
53+
print(f"Command Skipped: {skip}")
54+
return
55+
cfunc = getattr(cli, "cfunc", _default)
56+
cfunc(ctx)
57+
return
58+
59+
60+
if __name__ == "__main__":
61+
main()
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#
2+
# sambacc: a samba container configuration tool
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+
import argparse
20+
import logging
21+
import signal
22+
import sys
23+
import typing
24+
25+
from ..cli import Context, Fail, commands
26+
27+
_logger = logging.getLogger(__name__)
28+
_MTLS = "mtls"
29+
_FORCE = "force"
30+
31+
32+
def _serve_args(parser: argparse.ArgumentParser) -> None:
33+
parser.add_argument(
34+
"--address",
35+
"-a",
36+
help="Specify an {address:port} value to bind to.",
37+
)
38+
# Force an explicit choice of (the only) rpc type in order to clearly
39+
# prepare the space for possible alternatives
40+
egroup = parser.add_mutually_exclusive_group(required=True)
41+
egroup.add_argument(
42+
"--grpc",
43+
dest="rpc_type",
44+
action="store_const",
45+
default="grpc",
46+
const="grpc",
47+
help="Use gRPC",
48+
)
49+
# security settings
50+
parser.add_argument(
51+
"--insecure",
52+
action="store_true",
53+
help="Disable TLS",
54+
)
55+
parser.add_argument(
56+
"--allow-modify",
57+
choices=(_MTLS, _FORCE),
58+
default=_MTLS,
59+
help="Control modification mode",
60+
)
61+
parser.add_argument(
62+
"--tls-key",
63+
help="Server TLS Key",
64+
)
65+
parser.add_argument(
66+
"--tls-cert",
67+
help="Server TLS Certificate",
68+
)
69+
parser.add_argument(
70+
"--tls-ca-cert",
71+
help="CA Certificate",
72+
)
73+
74+
75+
class Restart(Exception):
76+
pass
77+
78+
79+
@commands.command(name="serve", arg_func=_serve_args)
80+
def serve(ctx: Context) -> None:
81+
"""Start an RPC server."""
82+
83+
def _handler(*args: typing.Any) -> None:
84+
raise Restart()
85+
86+
signal.signal(signal.SIGHUP, _handler)
87+
while True:
88+
try:
89+
_serve(ctx)
90+
return
91+
except KeyboardInterrupt:
92+
_logger.info("Exiting")
93+
sys.exit(0)
94+
except Restart:
95+
_logger.info("Re-starting server")
96+
continue
97+
98+
99+
def _serve(ctx: Context) -> None:
100+
import sambacc.grpc.backend
101+
import sambacc.grpc.server
102+
103+
config = sambacc.grpc.server.ServerConfig()
104+
config.insecure = bool(ctx.cli.insecure)
105+
if ctx.cli.address:
106+
config.address = ctx.cli.address
107+
if not (ctx.cli.insecure or ctx.cli.tls_key):
108+
raise Fail("Specify --tls-key=... or --insecure")
109+
if not (ctx.cli.insecure or ctx.cli.tls_cert):
110+
raise Fail("Specify --tls-cert=... or --insecure")
111+
if ctx.cli.tls_key:
112+
config.server_key = _read(ctx, ctx.cli.tls_key)
113+
if ctx.cli.tls_cert:
114+
config.server_cert = _read(ctx, ctx.cli.tls_cert)
115+
if ctx.cli.tls_ca_cert:
116+
config.ca_cert = _read(ctx, ctx.cli.tls_ca_cert)
117+
config.read_only = not (
118+
ctx.cli.allow_modify == _FORCE
119+
or (not config.insecure and config.ca_cert)
120+
)
121+
122+
backend = sambacc.grpc.backend.ControlBackend(ctx.instance_config)
123+
sambacc.grpc.server.serve(config, backend)
124+
125+
126+
def _read(ctx: Context, path_or_url: str) -> bytes:
127+
with ctx.opener.open(path_or_url) as fh:
128+
content = fh.read()
129+
return content if isinstance(content, bytes) else content.encode()

0 commit comments

Comments
 (0)