Hemdal.Host behaviour (Hemdal v1.0.3)

View Source

Host is spawning processes to run tasks for the corresponding host. It's a way of limiting the amount of commands triggered against a host and ensure the rest of the commands stay in the queue.

It's also the base for the implementation of the way of running commands. At the moment the available way to run commands are:

  • Hemdal.Host.Local intended to run local commands.
  • Hemdal.Host.Trooper intended to run SSH commands. It's not included in the main repository, you can find more information here.

If you want to know more about what could be included in a command or to be run in a host you can review the following modules:

Implement a new Host

If you need to implement a new way to run external commands, you can create a new Hemdal.Host.XXX module which will be using the module Hemdal.Host inside. For example, if you need to implement a telnet way to access to the remote hosts, you can implement a module as follows:

defmodule Hemdal.Host.Telnet do
  use Hemdal.Host

  @impl Hemdal.Host
  def exec(host, command) do
    # do your stuff
    {:ok, errorlevel, output}
  end

  @impl Hemdal.Host
  def write_file(host, filename, content) do
    raise "Impossible to transfer files"
  end

  @impl Hemdal.Host
  def delete(host, filename) do
    raise "Impossible to remove files"
  end
end

While telnet isn't prepared to transfer files, it's raising an error which is telling the system that's impossible to use the script way for running commands.

Summary

Types

The command to be executed.

The command arguments to be passed with the execution command.

The errorlevel is shell concept. In the shell every command is returning an integer as the errorlevel after the running, if that's 0 (zero), it's meaning the execution was fine, otherwise a positive or negative number means the running was wrong and the number could means something different depending on the command.

Handler is used by the backend implementation of the host, it could be whatever depending on the needs of the backend implementation. For example, it could be the SSH connection or the credentials, or the way to access to the PTY or TTY. See the implementations for further details.

The console output. It will be decoded using JSON to determine what's the information processed and generated by the command to be processed.

The reason of the failure. It's usually an atom, but it could be whatever.

t()

Callbacks

Remove a file which was created with write_file/3 when the execution of the script was finalised.

Exec a command using the method implemented by the module where it's implemented. The exec/2 command is getting a handler from the transaction and the command to be executed as a string.

The transaction is an optional callback which is implemented by default if you are using use Hemdal.Host and it's ensuring you can provide to the write_file/3, exec/2 and delete/2 the same handler which must be provided to the running function. As an example, the default implementation of this callback is as follows

Write a file in the remote (or local) host. It's intended to write the scripts which will be needed to be executed after that with exec/2.

Functions

Returns a specification to start this module under a supervisor.

Run or execute the command passed as parameter. It's needed to pass the host ID to find the process where to send the request, and the the command and the arguments to run the command.

Check if the host is started based on the host ID passed as parameter.

Get all the host IDs that are running at the moment of calling this function.

Retrieve the PID providing the host ID.

Performs a reload for all of the hosts. It's retrieving the configuration for all of the hosts and applying it for each host. It's reloading the configuration for each host and applying it through the update_host/1 function.

Start a new host under the Hemdal.Host.Supervisor module.

Start all of the hosts based on the configuration. It's retrieving all of the hosts from the configuration and the using each host with the start/1 function.

Start a new host directly. It's intended to be in use from the supervisor, but it could be used for test purposes.

Terminate the processes related to a process ID.

Update the host configuration. If the host isn't running it's starting it and passing it the configuration provided as the host parameter. In a nutshell, it's: if exist host ID then perform an update of the host information, else start the process with the host information.

Types

command()

@type command() :: String.t()

The command to be executed.

command_args()

@type command_args() :: [String.t()]

The command arguments to be passed with the execution command.

errorlevel()

@type errorlevel() :: integer()

The errorlevel is shell concept. In the shell every command is returning an integer as the errorlevel after the running, if that's 0 (zero), it's meaning the execution was fine, otherwise a positive or negative number means the running was wrong and the number could means something different depending on the command.

handler()

@type handler() :: any()

Handler is used by the backend implementation of the host, it could be whatever depending on the needs of the backend implementation. For example, it could be the SSH connection or the credentials, or the way to access to the PTY or TTY. See the implementations for further details.

output()

@type output() :: String.t()

The console output. It will be decoded using JSON to determine what's the information processed and generated by the command to be processed.

reason()

@type reason() :: any()

The reason of the failure. It's usually an atom, but it could be whatever.

t()

@type t() :: %Hemdal.Host{
  host: nil | Hemdal.Config.Host.t(),
  max_workers: :infinity | non_neg_integer(),
  queue: :queue.queue({GenServer.from(), command(), command_args()}),
  workers: non_neg_integer()
}

Callbacks

delete(handler, tpm_file)

@callback delete(handler(), tpm_file :: charlist()) :: :ok | {:error, reason()}

Remove a file which was created with write_file/3 when the execution of the script was finalised.

exec(handler, command)

@callback exec(handler(), command()) :: {:ok, errorlevel(), output()} | {:error, reason()}

Exec a command using the method implemented by the module where it's implemented. The exec/2 command is getting a handler from the transaction and the command to be executed as a string.

transaction(t, function)

@callback transaction(Hemdal.Config.Host.t(), (handler() -> any())) :: any()

The transaction is an optional callback which is implemented by default if you are using use Hemdal.Host and it's ensuring you can provide to the write_file/3, exec/2 and delete/2 the same handler which must be provided to the running function. As an example, the default implementation of this callback is as follows:

@impl Hemdal.Host
def transaction(host, f), do: f.(host)

write_file(handler, tmp_file, content)

@callback write_file(handler(), tmp_file :: String.t(), content :: String.t()) ::
  :ok | {:error, reason()}

Write a file in the remote (or local) host. It's intended to write the scripts which will be needed to be executed after that with exec/2.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

exec(host_id, cmd, args)

@spec exec(host_id(), Hemdal.Config.Alert.Command.t(), command_args()) ::
  {:ok, map()} | {:error, map()}

Run or execute the command passed as parameter. It's needed to pass the host ID to find the process where to send the request, and the the command and the arguments to run the command.

It's returning a tuple for :ok or :error and a set of data which depends on if it was success or not.

The success return data is usually including the following keys (all of them as strings):

  • status which could be OK, FAIL, WARN or UNKNOWN.
  • message which is a string containing a message to show.

The failure return data is usually similar to the previous one but it could be containing something different depending on the return of the remote command. If the JSON sent back from the command is valid, it's usually using that as data and it's marked as UNKNOWN status if there is no status defined.

exists?(host_id)

@spec exists?(host_id()) :: boolean()

Check if the host is started based on the host ID passed as parameter.

get_all()

@spec get_all() :: [host_id()]

Get all the host IDs that are running at the moment of calling this function.

get_pid(host_id)

@spec get_pid(host_id()) :: pid() | nil

Retrieve the PID providing the host ID.

reload_all()

@spec reload_all() :: :ok

Performs a reload for all of the hosts. It's retrieving the configuration for all of the hosts and applying it for each host. It's reloading the configuration for each host and applying it through the update_host/1 function.

start(host)

@spec start(Hemdal.Config.Host.t()) :: :ok

Start a new host under the Hemdal.Host.Supervisor module.

start_all()

@spec start_all() :: :ok

Start all of the hosts based on the configuration. It's retrieving all of the hosts from the configuration and the using each host with the start/1 function.

start_link(list)

@spec start_link([Hemdal.Config.Host.t()]) :: {:ok, pid()}

Start a new host directly. It's intended to be in use from the supervisor, but it could be used for test purposes.

stop(host_id)

@spec stop(host_id()) :: :ok

Terminate the processes related to a process ID.

update_host(host)

@spec update_host(Hemdal.Config.Host.t()) :: {:ok, pid()}

Update the host configuration. If the host isn't running it's starting it and passing it the configuration provided as the host parameter. In a nutshell, it's: if exist host ID then perform an update of the host information, else start the process with the host information.