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 is used to set the hostname on an ubuntu system
and embodies several key points.
1. Prior to running the realbook script, a `:hostname` variable
has to be set (not included in this code)
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_tty!("apt list #{item}") =~ "installed"
end)
end
play do
# note this will forward the apt output to the local console.
sudo_run_tty! "apt-get -y install " <> Enum.join(@items, "\s"), stdout: :stdout
end
```
## Assets
You may load binary assets in an assets directory using
`Realbook.Commands.asset!/1` macro.
`Application.get_env(:realbook, :assets_dir)` must be set, and the file
must exist, or the realbook will fail to trigger.
## 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 ~w(my_script_1 my_script_2 my_script_3)
```
You're allowed to omit `Realbook.Macros.play/1` and
`Realbook.Macros.verify/1` directives if you have a
`Realbook.Macros.requires/1` statement.
If you want to use this as a sole entry point for a series of realbooks,
you should consider using `Realbook.sigil_B/2`.
## Shipping realbooks as statically compiled `.ex` files
Realbook comes with mix task, `mix realbook.gen` which will create files
in `lib/realbook/scripts` that can be used for generating statically-compiled
realbooks. These can be directly shipped with your application as code.
## 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.