Recipe basics¶
A recipe is a Ruby file that declares the desired state of a machine as a
list of resources. Sadame evaluates the file from top to bottom, collects the
resources in the order they appear, and then plans or applies them in that
order. Because the recipe is ordinary Ruby, loops, conditionals and helper
methods need no special syntax. This page covers how a recipe directory is
laid out, where vars come from, how include_recipe splits a recipe into
files, and how templates work.
A complete example¶
The example used on this page lives in examples/basics:
examples/basics/
recipe.rb # entry point
vars.rb # default vars
parts/motd.rb # included recipe
parts/templates/motd.erb # template used by parts/motd.rb
# Packages every machine in this example gets. vars come from vars.rb and
# --var; a recipe is plain Ruby, so a loop is just a loop.
vars[:base_packages].each do |name|
package name
end
directory "/etc/sadame-demo", mode: "0755"
# Paths in include_recipe are relative to this file; ".rb" is optional.
include_recipe "parts/motd"
# vars.rb is plain Ruby; its last expression must be a Hash.
{
team: "platform",
contact: "ops@example.com",
base_packages: ["curl", "less"],
maintenance: nil
}
# Templates are looked up next to this file, then in its templates/ directory.
file "/etc/motd", template: "motd.erb", mode: "0644"
This machine is managed by sadame. Local changes will be overwritten.
Team: <%= vars[:team] %>
Contact: <%= vars[:contact] %>
<% if vars[:maintenance] -%>
Maintenance window: <%= vars[:maintenance] %>
<% end -%>
Planned on a fresh Debian 12 machine:
$ sudo sadame plan examples/basics
sadame 0.5.1 plan on local (Debian GNU/Linux 12 (bookworm))
+ package[curl]
state: absent -> installed
+ package[less]
state: absent -> installed
+ directory[/etc/sadame-demo]
state: absent -> present
mode: 0755
~ file[/etc/motd]
--- /etc/motd
+++ /etc/motd (desired)
@@ -1,7 +1,3 @@
-
-The programs included with the Debian GNU/Linux system are free software;
-the exact distribution terms for each program are described in the
-individual files in /usr/share/doc/*/copyright.
-
-Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
-permitted by applicable law.
+This machine is managed by sadame. Local changes will be overwritten.
+Team: platform
+Contact: ops@example.com
Plan: 3 to create, 1 to update, 0 to remove, 0 to run, 0 unchanged.
The recipe file¶
- Each resource is a method call:
type "name", option: value, .... The name identifies the resource: a package name, a path, a command, a user name. Together they form the resource id,type[name], which shows up in output and is used bynotifies:. - An id may appear only once. Declaring
package "git"twice is an error that names the file and line. - Options are checked when the recipe loads. A misspelled option fails the whole run, with exit status 2, before anything is changed:
$ sudo sadame plan
sadame: recipe.rb:2: file[/etc/motd]: unknown option :contents (valid options: content, group, locals, mode, notifies, owner, sensitive, source, state, subscribes, tags, template, verify)
$ echo $?
2
- Ruby code in a recipe runs every time the recipe is loaded, for
planas well asapply. Use it to decide what to declare; make changes only through resources.
Besides the resources, a recipe can use:
| Name | What it is |
|---|---|
vars |
Hash of variables with symbol keys (see below) |
platform.family |
"debian", "rhel", "alpine", "arch" or nil |
platform.package_manager |
"apt", "dnf", "apk", "pacman" or nil |
platform.id, platform.version, platform.codename, platform.name |
ID, VERSION_ID, VERSION_CODENAME and PRETTY_NAME from /etc/os-release |
facts[:cpus], facts[:memory_mb], facts[:ipv4] and more |
what the machine has, read from it; see Facts |
host_name, host_groups |
the host's name and groups from the inventory; see Inventory |
include_recipe "path" |
evaluate another recipe file; tags: tags everything it declares (see Tags) |
For example, a package whose name differs between distributions:
# Package names are the distribution's own; pick one with plain Ruby.
web_server = platform.family == "debian" ? "apache2" : "httpd"
package web_server
vars¶
vars is a plain Hash with symbol keys. In this release it is filled from
two places, highest precedence first:
| Precedence | Source | Example |
|---|---|---|
| 1 (highest) | --var key=value on the command line, repeatable |
--var workers=8 |
| 2 | vars.rb next to the recipe |
{ workers: 4 } |
vars.rb is Ruby whose last expression is a Hash; it can compute values,
for example read an environment variable. Keys may be symbols or strings and
always become symbols.
Values given with --var are strings, except:
| Given | Becomes |
|---|---|
true, false |
true, false |
digits with an optional leading -, and no leading zero |
Integer: 8, -2, 0 |
| anything else | String: "0644", "1.5", "web01" |
Adding a maintenance window for one run changes the template output:
$ sudo sadame plan examples/basics --var maintenance='Sundays 02:00-03:00 UTC'
sadame 0.5.1 plan on local (Debian GNU/Linux 12 (bookworm))
+ package[curl]
state: absent -> installed
+ package[less]
state: absent -> installed
+ directory[/etc/sadame-demo]
state: absent -> present
mode: 0755
~ file[/etc/motd]
--- /etc/motd
+++ /etc/motd (desired)
@@ -1,7 +1,4 @@
-
-The programs included with the Debian GNU/Linux system are free software;
-the exact distribution terms for each program are described in the
-individual files in /usr/share/doc/*/copyright.
-
-Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
-permitted by applicable law.
+This machine is managed by sadame. Local changes will be overwritten.
+Team: platform
+Contact: ops@example.com
+Maintenance window: Sundays 02:00-03:00 UTC
Plan: 3 to create, 1 to update, 0 to remove, 0 to run, 0 unchanged.
include_recipe¶
include_recipe "parts/motd" evaluates parts/motd.rb. The path is
relative to the file that contains the call, and .rb is optional. The
included file shares vars and adds its resources at that point in the
order. A file included more than once is evaluated only the first time, so
two roles can both include a common base recipe.
Templates and source files used by an included recipe are looked up relative
to that file, not the entry point. That keeps a directory such as parts/
or roles/web/ self-contained.
Templates¶
file with template: renders an ERB template. Sadame implements the
commonly used part of ERB itself, because the single binary cannot ship
Ruby's ERB library:
| Tag | Effect |
|---|---|
<%= expression %> |
inserts the result of expression |
<% code %> |
runs Ruby code: if, each, assignments |
<%# comment %> |
produces nothing |
-%> |
at the end of any tag: also removes the newline that follows |
<%- |
at the start of a tag: also removes the indentation before it |
<%% |
a literal <% |
Inside a template, vars is the run's vars, locals is the Hash given
to the file resource's locals: option, and facts are the machine's
facts. Nothing else from the recipe is visible.
The template is looked up in the recipe's directory first, then in its
templates/ subdirectory. parts/motd.rb above uses motd.erb, found at
parts/templates/motd.erb.
After apply, changing a var shows exactly which lines of the rendered file
change:
$ sudo sadame plan examples/basics --var contact=platform@example.com
sadame 0.5.1 plan on local (Debian GNU/Linux 12 (bookworm))
~ file[/etc/motd]
--- /etc/motd
+++ /etc/motd (desired)
@@ -1,3 +1,3 @@
This machine is managed by sadame. Local changes will be overwritten.
Team: platform
-Contact: ops@example.com
+Contact: platform@example.com
Plan: 0 to create, 1 to update, 0 to remove, 0 to run, 3 unchanged.
An error in a template fails that resource and names the template and
line. Here templates/app.conf.erb contains port = <%= port %>:
$ sudo sadame plan
sadame 0.5.1 plan on local (Debian GNU/Linux 12 (bookworm))
x file[/etc/app.conf]
error: templates/app.conf.erb:3: undefined method 'port' for Sadame::Template::Scope
Plan: 0 to create, 0 to update, 0 to remove, 0 to run, 0 unchanged, 1 failed.
$ echo $?
2
Here the template used port instead of locals[:port]. The wording after
the line number comes from the Ruby runtime and differs slightly between the
single binary and CRuby.
Common mistakes¶
- String keys in
vars. Keys are always symbols:vars[:team], notvars["team"], which returnsnil. - Blank lines from control tags.
<% if x %>on its own line leaves an empty line in the output, as in ERB. End such tags with-%>. - Expecting
--var port=08080to be a number. Leading zeros keep a value a string, so modes like0644survive. - Side effects in Ruby code.
File.writeor backticks at the top of a recipe run duringplantoo. Useexecwith a guard instead. - Templates next to the wrong file. A template used in
roles/web.rbbelongs inroles/orroles/templates/, not next to the entry recipe.
See also¶
- file for
template:,source:andlocals:. - exec for commands with guards.
- Getting started for reading plan output.