123 lines
4.0 KiB
Python
123 lines
4.0 KiB
Python
"""Remote machine service definition.
|
|
|
|
An ``remote_machine`` instance is an SSH endpoint that can run reusable saved tasks.
|
|
Tasks themselves stay in the global saved-task registry; the instance only owns
|
|
transport (host/port/user/key). Every run is recorded in ``service_task_runs``
|
|
and shown as history on the instance's service page.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from media_library_viewer_api.integrations.base import (
|
|
SecretField,
|
|
ServiceConfigBase,
|
|
ServiceDefinition,
|
|
TestResult,
|
|
WidgetConfigBase,
|
|
translate_connection_error,
|
|
widget_kind,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from media_library_viewer_api.services.settings_store import SettingsStore
|
|
|
|
|
|
def test_connection(
|
|
config: dict[str, Any],
|
|
secrets: dict[str, str],
|
|
store: SettingsStore,
|
|
) -> TestResult:
|
|
"""Build an SSH client via build_ssh_client and attempt .connect().
|
|
|
|
Reuses the same error-translation patterns as test_machine_ssh (banner,
|
|
auth failed). Known-host recording is preserved.
|
|
"""
|
|
from media_library_viewer_api.services.task_runner import build_ssh_client
|
|
from media_library_viewer_api.widgets.sources import ServiceRecord
|
|
|
|
host = str(config.get("host") or "").strip()
|
|
port = int(config.get("port") or 22)
|
|
try:
|
|
service = ServiceRecord(
|
|
id="",
|
|
service_type="remote_machine",
|
|
name="test",
|
|
config=config,
|
|
secrets=secrets,
|
|
enabled=True,
|
|
)
|
|
client = build_ssh_client(store, service)
|
|
try:
|
|
client.connect()
|
|
except Exception as exc:
|
|
lowered = str(exc).lower()
|
|
if "protocol banner" in lowered:
|
|
return TestResult(
|
|
ok=False,
|
|
detail=f"SSH banner not received from {host}:{port}; confirm the SSH service is running.",
|
|
)
|
|
if "no authentication methods available" in lowered or "authentication failed" in lowered:
|
|
return TestResult(
|
|
ok=False,
|
|
detail=f"SSH authentication failed for {host}:{port}; check the SSH key, passphrase, or username.",
|
|
)
|
|
return translate_connection_error(exc, context=f"SSH {host}:{port}")
|
|
finally:
|
|
client.close()
|
|
return TestResult(
|
|
ok=True,
|
|
detail=f"SSH connection succeeded for {host}:{port}.",
|
|
evidence=f"Connected to {host}:{port}",
|
|
)
|
|
except ValueError as exc:
|
|
return TestResult(ok=False, detail=str(exc))
|
|
except Exception as exc:
|
|
return translate_connection_error(exc, context=f"SSH {host}:{port}")
|
|
|
|
|
|
class RemoteMachineConfig(ServiceConfigBase):
|
|
"""Non-secret Remote machine config.
|
|
|
|
The SSH key itself lives in the saved SSH-key registry and is referenced by
|
|
``ssh_key_id``. An optional ``passphrase`` is stored as a secret.
|
|
"""
|
|
|
|
host: str
|
|
port: int = 22
|
|
username: str = ""
|
|
ssh_key_id: str = ""
|
|
timeout_seconds: int = 30
|
|
|
|
|
|
class RemoteMachineTaskOutputWidgetConfig(WidgetConfigBase):
|
|
"""Output of a saved task run on this instance."""
|
|
|
|
task_id: str
|
|
# service_id is implicit (the widget's service); allow overriding per-widget.
|
|
service_id: str | None = None
|
|
|
|
|
|
DEFINITION = ServiceDefinition(
|
|
service_type="remote_machine",
|
|
name="Remote machine",
|
|
description="SSH transport for files and reusable actions.",
|
|
config_model=RemoteMachineConfig,
|
|
secret_fields=[
|
|
SecretField(key="passphrase", label="Key passphrase", helper="Optional"),
|
|
SecretField(key="password", label="SSH password", helper="Optional"),
|
|
],
|
|
widget_kinds=[
|
|
widget_kind(
|
|
kind="task_output",
|
|
name="Task output",
|
|
description="Output of a saved task run.",
|
|
model_cls=RemoteMachineTaskOutputWidgetConfig,
|
|
default_config={"task_id": ""},
|
|
refresh_interval_ms=0,
|
|
),
|
|
],
|
|
test_callable=test_connection,
|
|
)
|