# `Hemdal.Host`
[🔗](https://github.com/altenwald/hemdal/blob/v1.4.0/lib/hemdal/host.ex#L1)

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](https://github.com/altenwald/hemdal_trooper).

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:

- `Hemdal.Config.Command` where you can check what's included
  inside of the command.
- `Hemdal.Config.Host` where you can find information about the host.

## 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:

```elixir
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.

# `command`

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

The command to be executed.

# `command_args`

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

The command arguments to be passed with the execution command.

# `errorlevel`

```elixir
@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.

# `exec_mod_opts`

```elixir
@type exec_mod_opts() :: [output: boolean(), timeout: timeout()]
```

The options for `exec_interactive/4` and `shell/2` let us define if we want to
accumulate the output (it's usually not desirable for shell sessions) and the
timeout for idle. It means the time it wait between interactions to close the
communication.

# `exec_opts`

```elixir
@type exec_opts() ::
  [timeout: timeout(), args: [String.t()], caller: pid()] | [String.t()]
```

The options for exec are providing to the execution valid information, and
sometimes required information about the running process.

For example, if we want to run an interactive command but using a
non-background execution, then we will need to provide `caller` option.

In addition, `args` are a list of arguments we pass to a script usually,
and `timeout` makes sense mainly for non-interactive executions. Indeed,
`timeout` is only applicable for the calling of the function, because
it returns usually fast (depending on the loaded queue), it's not very
useful for execution in background mode, and it could be dangerous in
interactive and non-background mode.

# `handler`

```elixir
@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.

# `host_stats`

```elixir
@type host_stats() :: %{
  :status =&gt; :up | :down,
  optional(:waiting_workers) =&gt; non_neg_integer(),
  optional(:running_workers) =&gt; non_neg_integer(),
  optional(:max_running_workers) =&gt; non_neg_integer()
}
```

Information about the status of the host. The host is limiting the number
of running workers so we can get the information of the following data:

- `waiting_workers` the current number of requests awaiting in the queue.
- `running_workers` the number of running workers.
- `max_running_workers` the max number of workers that could be run at
  the same time.

# `output`

```elixir
@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`

```elixir
@type reason() :: any()
```

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

# `delete`

```elixir
@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 finalized.

# `exec`

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

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

# `exec_interactive`

```elixir
@callback exec_interactive(handler(), command(), pid(), exec_mod_opts()) ::
  {:ok, pid()} | {:ok, errorlevel(), output()} | {:error, reason()}
```

Exec an interactive command implemented by the module where it's
implemented. The `c:exec_interactive/3` command is getting a handler from the
transaction and the command to be executed as a string.

# `shell`

```elixir
@callback shell(handler(), pid(), exec_mod_opts()) ::
  {:ok, pid()} | {:ok, errorlevel(), output()} | {:error, reason()}
```

Start a shell and connect to it. The shell usually has specific features that let
us to do more than in normal or usual exec commands.

# `transaction`

```elixir
@callback transaction(Hemdal.Config.Host.t(), (handler() -&gt; 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:

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

# `write_file`

```elixir
@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`.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `exec`

```elixir
@spec exec(host_id(), Hemdal.Config.Command.t(), exec_opts()) ::
  {: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.

# `exec_background`

```elixir
@spec exec_background(host_id(), Hemdal.Config.Command.t(), exec_opts()) ::
  {:ok, pid()} | {:error, any()}
```

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`. The `:ok` tuple have a PID to know
which process is in charge of running the background process and to know if it's
still running or not.

# `exists?`

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

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

# `get_all`

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

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

# `get_all_stats`

```elixir
@spec get_all_stats() :: %{required(host_id()) =&gt; host_stats()}
```

Retrieve stats for all of the hosts. See `get_stats/1`.

# `get_pid`

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

Retrieve the PID providing the host ID.

# `get_stats`

```elixir
@spec get_stats(host_id()) :: host_stats()
```

Retrieve stats for a specific host.

# `reload_all`

```elixir
@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`

```elixir
@spec start(Hemdal.Config.Host.t()) :: {:ok, pid()} | {:error, any()}
```

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

# `start_all`

```elixir
@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`

```elixir
@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`

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

Terminate the processes related to a process ID.

# `update_host`

```elixir
@spec update_host(Hemdal.Config.Host.t()) :: {:ok, pid()} | {:error, any()}
```

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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
