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.

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