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
173 lines
5.6 KiB
Python
173 lines
5.6 KiB
Python
#!/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"],
|
|
)
|