145 lines
4 KiB
Elixir
145 lines
4 KiB
Elixir
defmodule Prymn.Agents do
|
|
@moduledoc ~S"""
|
|
Prymn Agents are programs that manage a remote client machine. Prymn backend
|
|
communicates with them using GRPC calls. GRPC connections are started using
|
|
the Prymn.Agents.ConnectionSupervisor and are book-kept using the
|
|
Prymn.Agents.Registry.
|
|
|
|
Agents are only valid when a `Prymn.Servers.Server` is considered registered.
|
|
|
|
"""
|
|
|
|
require Logger
|
|
alias Prymn.Agents.{Connection, Health, Agent}
|
|
alias PrymnProto.Prymn.Agent.Stub
|
|
alias PrymnProto.Prymn.{ExecRequest, SysUpdateRequest}
|
|
|
|
@doc """
|
|
Establish a connection with a Server if one does not already exist, and
|
|
return an Agent that interfaces with the rest of the system.
|
|
|
|
## Examples
|
|
iex> Prymn.Servers.get_server_by_ip!("127.0.0.1") |> Prymn.Agents.from_server()
|
|
%Prymn.Agents.Agent{}
|
|
"""
|
|
def from_server(%Prymn.Servers.Server{status: :registered} = server) do
|
|
case start_connection(server.public_ip) do
|
|
{:ok, _pid} -> Agent.new(server.public_ip)
|
|
{:error, error} -> {:error, error}
|
|
end
|
|
end
|
|
|
|
def from_server(%Prymn.Servers.Server{}) do
|
|
Logger.error("Tried to establish a connection with an unregistered server.")
|
|
{:error, :unauthorized_action}
|
|
end
|
|
|
|
@doc """
|
|
Establish a connection with a Server if one does not already exist for a
|
|
given App. Returns an [Agent] that interfaces with the rest of the system.
|
|
"""
|
|
def from_app(%Prymn.Apps.App{} = app) do
|
|
app = Prymn.Repo.preload(app, :server)
|
|
from_server(app.server)
|
|
end
|
|
|
|
@doc """
|
|
Starts a new connection with `host_address` if one does not exist.
|
|
|
|
## Examples
|
|
iex> Prymn.Agents.start_connection("127.0.0.1")
|
|
{:ok, <PID:1234>}
|
|
iex> Prymn.Agents.start_connection("127.0.0.1")
|
|
{:ok, <PID:1234>}
|
|
"""
|
|
def start_connection(host_address) do
|
|
spec = {Connection, host_address}
|
|
|
|
case DynamicSupervisor.start_child(Prymn.Agents.ConnectionSupervisor, spec) do
|
|
{:ok, pid} -> {:ok, pid}
|
|
{:error, {:already_started, pid}} -> {:ok, pid}
|
|
{:error, error} -> {:error, error}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Subscribe to the host's Health using Phoenix.PubSub.
|
|
Broadcasted messages are the Health struct:
|
|
|
|
%Prymn.Agents.Health{}
|
|
"""
|
|
def subscribe_to_health(%Agent{} = agent) do
|
|
:ok = Health.subscribe(agent.host_address)
|
|
agent
|
|
end
|
|
|
|
def subscribe_to_health(host_address) do
|
|
:ok = Health.subscribe(host_address)
|
|
end
|
|
|
|
# TODO
|
|
# def alive?(host_address) do
|
|
# end
|
|
|
|
@doc """
|
|
Return the last known health status of the Agent, or `nil` if it doesn't
|
|
exist.
|
|
"""
|
|
def get_health(host_address) do
|
|
Health.lookup(host_address)
|
|
end
|
|
|
|
@doc """
|
|
Get the system's information (CPU, Memory usage, etc.).
|
|
"""
|
|
def get_sys_info(%Agent{} = agent) do
|
|
{:error, :unimplemented}
|
|
end
|
|
|
|
@doc """
|
|
Run a command.
|
|
"""
|
|
def exec(%Agent{} = agent, %ExecRequest{} = request) do
|
|
with {:ok, channel} <- get_channel(agent),
|
|
{:ok, result} <- Stub.exec(channel, request) do
|
|
result
|
|
else
|
|
{:error, error} -> {:error, error}
|
|
end
|
|
end
|
|
|
|
def exec(%Agent{} = agent, request) when is_map(request),
|
|
do: exec(agent, struct(ExecRequest, request))
|
|
|
|
@doc """
|
|
Perform a system update.
|
|
"""
|
|
def sys_update(%Agent{} = agent, %SysUpdateRequest{} = request) do
|
|
with {:ok, channel} <- get_channel(agent),
|
|
{:ok, result} <- Stub.sys_update(channel, request) do
|
|
result
|
|
else
|
|
{:error, error} -> {:error, error}
|
|
end
|
|
end
|
|
|
|
def sys_update(%Agent{} = agent, request) when is_map(request),
|
|
do: sys_update(agent, struct(SysUpdateRequest, request))
|
|
|
|
def terminal(%Agent{} = agent) do
|
|
# TODO: Find a better solve for bi-directional GRPC stream
|
|
with {:ok, channel} <- get_channel(agent),
|
|
stream <- Stub.terminal(channel) do
|
|
stream
|
|
else
|
|
{:error, error} -> {:error, error}
|
|
end
|
|
end
|
|
|
|
defp get_channel(%Agent{} = agent) do
|
|
case start_connection(agent.host_address) do
|
|
{:ok, pid} -> {:ok, Connection.get_channel(pid)}
|
|
{:error, error} -> {:error, error}
|
|
end
|
|
end
|
|
end
|