Current section
Files
Jump to
Current section
Files
guides/2.writing_a_realbook.md
# Writing a Realbook
## A basic example
To describe how to write a realbook let's use a real example:
```elixir
verify do
(run! "hostname") == (get :hostname)
end
play do
hostname = get :hostname
sudo_run! "hostnamectl set-hostname #{hostname}"
end
```
This simple example embodies several key points.
1. in this case prior to running the realbook script, a hostname variable
has to be set.
2. according to the verify clause, the hostname variable should match
what we expect to be the remote hostname to be, via the `hostname` command.
3. if it doesn't match (which it won't if it's the first initialization),
then we should reset the hostname using the `hostnamectl` command which
must be superuser to be run.
### Verification strategies
`verify` blocks fail if *either* there is a `raise`ing event inside the block
or if the block returns a falsy value (`nil` or `false`). Here are some
tips for concise and legible verifications:
- use `raise`ing clauses aggressively and early in your verification
- wait for boolean checks (for example, comparing checksum values)
- if you must raise early on a boolean, use the following strategy featuring
the `Realbook.Commands.fail/1` macro:
```elixir
defp matches_checksum?(value), do: value == @checksum
verify do
# some code here
matches_checksum?(thing_to_check) or fail("checksum failed!")
# more code
end
```
## Software Installers
Typically, installers expect the running system to be a `tty` system.
This is the case, for example for `ubuntu` systems using the `apt` package
manager.
The following example shows using module attributes as constants, as well
as well as using `run!/2` and `sudo_run!/2` with the `tty` option.
```elixir
@items ~w(apparmor fail2ban)
verify do
Enum.all?(@items, fn item ->
# note that we should redirect stdout to the main stdout stream in
# order be able to verify that it's installed.
run!("apt list #{item}", tty: true, stdout: :stream) =~ "installed"
end)
end
play do
# note this will forward the apt status reports to the local console.
sudo_run! ("apt-get -y install " <> Enum.join(@items, "\s")), tty: true
end
```
## Unconditional execution
If you want to unconditionally execute a `play` block, use the `verify false`
directive. Note that such a script will not perform a postverification, so
if you must perform some verification of your results, you should leave those
at the tail of your `play` block.
## Multi-Realbooks
It's possible to write a realbook which is the chained composition of several
realbooks, the following is a trivial realbook which only serves to execute
a series of other scripts:
```elixir
requires ["my_script_1.exs", "my_script_2.exs", "my_script_3.exs"]
verify false
play do
end
```
## Other goodies
Two more commands are automatically imported:
- `Realbook.Commands.sleep/1` which sleeps the realbook process.
- `Realbook.Commands.log/1` which creates a log event. Currently this does
not provide extra log metadata, but will do so in the future.