Writing a Realbook
A basic example
To describe how to write a realbook let's use a real example:
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.
- Prior to running the realbook script, a
:hostname
variable has to be set (not included in this code) - according to the verify clause, the hostname variable should match
what we expect to be the remote hostname to be, via the
hostname
command. - 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:
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.
@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
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:
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
.
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.