cli (serverless): New serverless commands:
New `serverless` commands. For now only `discover` and `info` are implemented:
- `discover` find other peers on LAN
- `info` get various infos on serverless status (for now, our own device ID,
advertised service name, and the number of found peers)
rel 479
This commit is contained in:
parent
2ae02c8e4a
commit
e013f9ba66
1 changed files with 173 additions and 0 deletions
173
libervia/cli/cmd_serverless.py
Normal file
173
libervia/cli/cmd_serverless.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Libervia CLI
|
||||
# Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""CLI commands for serverless."""
|
||||
|
||||
from rich.table import Table
|
||||
|
||||
from libervia.backend.core.i18n import _
|
||||
from libervia.backend.plugins.plugin_misc_serverless.models import (
|
||||
DiscoverNodes,
|
||||
Node,
|
||||
ServerlessInfo,
|
||||
)
|
||||
from libervia.cli.constants import Const as C
|
||||
|
||||
from . import base
|
||||
|
||||
__commands__ = ["Serverless"]
|
||||
|
||||
|
||||
class ServerlessInfos(base.CommandBase):
|
||||
"""Show serverless device info."""
|
||||
|
||||
def __init__(self, host) -> None:
|
||||
extra_outputs = {"default": self.default_output}
|
||||
super().__init__(
|
||||
host,
|
||||
"info",
|
||||
help=_("show serverless device information"),
|
||||
use_output=C.OUTPUT_MODEL,
|
||||
extra_outputs=extra_outputs,
|
||||
use_verbose=True,
|
||||
)
|
||||
|
||||
def add_parser_options(self) -> None:
|
||||
pass
|
||||
|
||||
def default_output(self, info: ServerlessInfo) -> None:
|
||||
self.console.print(f" Device ID: {info.device_id}")
|
||||
self.console.print(f" mDNS name: {info.service_name}")
|
||||
self.console.print(f" Peers found: {info.peer_count}")
|
||||
|
||||
async def start(self) -> None:
|
||||
try:
|
||||
data_s = await self.host.bridge.serverless_infos_get()
|
||||
info = ServerlessInfo.model_validate_json(data_s)
|
||||
except Exception as e:
|
||||
self.disp(f"Failed to get serverless info: {e}", error=True)
|
||||
self.host.quit(C.EXIT_BRIDGE_ERRBACK)
|
||||
return
|
||||
|
||||
await self.output(info)
|
||||
self.host.quit()
|
||||
|
||||
|
||||
class ServerlessDiscover(base.CommandBase):
|
||||
"""Discover peers on the local network."""
|
||||
|
||||
def __init__(self, host) -> None:
|
||||
extra_outputs = {"default": self.default_output}
|
||||
super().__init__(
|
||||
host,
|
||||
"discover",
|
||||
help=_("discover peers on the local network"),
|
||||
use_output=C.OUTPUT_MODEL,
|
||||
extra_outputs=extra_outputs,
|
||||
use_verbose=True,
|
||||
)
|
||||
|
||||
def add_parser_options(self) -> None:
|
||||
self.parser.add_argument(
|
||||
"-f",
|
||||
"--follow",
|
||||
action="store_true",
|
||||
help=_("keep monitoring for new/lost peers"),
|
||||
)
|
||||
|
||||
def _render_table(self, nodes: list[Node], title: str) -> Table:
|
||||
table = Table(title=title)
|
||||
table.add_column("👤 " + _("Name"))
|
||||
table.add_column("🪪 " + _("Device ID"))
|
||||
table.add_column("🖧 " + _("Addresses"))
|
||||
if self.verbosity:
|
||||
table.add_column("📋 " + _("Metadata"))
|
||||
for node in nodes:
|
||||
row = [node.name, node.device_id, ", ".join(node.addrs)]
|
||||
if self.verbosity:
|
||||
row.append(
|
||||
", ".join(f"{k}={v}" for k, v in node.metadata.items())
|
||||
if node.metadata
|
||||
else ""
|
||||
)
|
||||
table.add_row(*row)
|
||||
return table
|
||||
|
||||
def default_output(self, nodes: DiscoverNodes) -> None:
|
||||
if not nodes:
|
||||
self.console.print(_("No peers discovered."))
|
||||
return
|
||||
self.console.print(self._render_table(list(nodes), _("Discovered peers")))
|
||||
|
||||
async def _on_peer_found(self, node_json: str) -> None:
|
||||
node = Node.model_validate_json(node_json)
|
||||
self._peers[node.device_id] = node
|
||||
self.console.print("")
|
||||
self.console.print(self._render_table([node], _("[green]New peer found[/green]")))
|
||||
|
||||
async def _on_peer_lost(self, device_id: str) -> None:
|
||||
node = self._peers.pop(device_id, None)
|
||||
self.console.print("")
|
||||
if node is not None:
|
||||
self.console.print(self._render_table([node], _("[red]Peer lost[/red]")))
|
||||
else:
|
||||
self.console.print(
|
||||
_("[red]Peer lost:[/red] {device_id}").format(device_id=device_id)
|
||||
)
|
||||
|
||||
async def start(self) -> None:
|
||||
try:
|
||||
nodes_s = await self.host.bridge.serverless_discover("{}")
|
||||
nodes = DiscoverNodes.model_validate_json(nodes_s)
|
||||
except Exception as e:
|
||||
self.disp(f"Failed to discover peers: {e}", error=True)
|
||||
self.host.quit(C.EXIT_BRIDGE_ERRBACK)
|
||||
return
|
||||
|
||||
await self.output(nodes)
|
||||
|
||||
if not self.args.follow:
|
||||
self.host.quit()
|
||||
return
|
||||
|
||||
# Follow mode: subscribe to signals and keep running
|
||||
self._peers: dict[str, Node] = {node.device_id: node for node in nodes}
|
||||
self.host.bridge.register_signal(
|
||||
"serverless_peer_found", self._on_peer_found, "plugin"
|
||||
)
|
||||
self.host.bridge.register_signal(
|
||||
"serverless_peer_lost", self._on_peer_lost, "plugin"
|
||||
)
|
||||
|
||||
|
||||
class Serverless(base.CommandBase):
|
||||
"""Serverless mode commands."""
|
||||
|
||||
subcommands = (
|
||||
ServerlessInfos,
|
||||
ServerlessDiscover,
|
||||
)
|
||||
|
||||
def __init__(self, host) -> None:
|
||||
super().__init__(
|
||||
host,
|
||||
"serverless",
|
||||
use_profile=False,
|
||||
help=_("serverless mode commands"),
|
||||
aliases=["sl"],
|
||||
)
|
||||
Loading…
Reference in a new issue