Skip to content

Migrate from Itamae

Sadame's recipe language comes from the same family as Itamae's and mitamae's: Ruby files that declare resources, evaluated top to bottom, with templates in ERB. Porting is mostly mechanical: resource names stay, block attributes become keyword options, and node becomes vars. This guide lists what is identical, what changed, and walks through examples/migrate-from-itamae, an Itamae recipe and its translation, which the test suite applies on Debian and Ubuntu.

Side by side

examples/migrate-from-itamae/itamae/recipe.rb
# The Itamae recipe that ../recipe.rb translates, kept for comparison.
# Run with Itamae: itamae local itamae/recipe.rb -j itamae/node.json
package 'nginx'
package 'openssl'

template '/etc/nginx/conf.d/app.conf' do
  source 'templates/app.conf.erb'
  mode '644'
  variables(server_name: node[:server_name], workers: node[:workers])
  notifies :reload, 'service[nginx]'
end

directory '/srv/app' do
  owner 'www-data'
  mode '755'
end

remote_file '/usr/local/bin/backup' do
  source 'files/backup.sh'
  mode '755'
end

execute 'self-signed certificate' do
  command 'openssl req -x509 -newkey rsa:2048 -nodes -days 365 -subj /CN=app.example.com ' \
          '-keyout /etc/ssl/private/app.key -out /etc/ssl/certs/app.crt'
  not_if 'test -f /etc/ssl/private/app.key'
end

user 'deploy' do
  shell '/bin/bash'
end

service 'nginx' do
  action [:enable, :start]
end
examples/migrate-from-itamae/recipe.rb
# itamae/recipe.rb, translated. Resource names mostly stay; blocks become
# keyword options; node becomes vars; template variables become locals.
package "nginx"
package "openssl"

file "/etc/nginx/conf.d/app.conf",
     template: "app.conf.erb",
     locals: { server_name: vars[:server_name], workers: vars[:workers] },
     mode: "644",
     notifies: [:reload, "service[nginx]"]

directory "/srv/app", owner: "www-data", mode: "755"

file "/usr/local/bin/backup", source: "backup.sh", mode: "755"

exec "self-signed certificate",
     command: "openssl req -x509 -newkey rsa:2048 -nodes -days 365 -subj /CN=app.example.com " \
              "-keyout /etc/ssl/private/app.key -out /etc/ssl/certs/app.crt",
     not_if: "test -f /etc/ssl/private/app.key"

user "deploy", shell: "/bin/bash"

service "nginx", enable: true, state: :running

The template changes in one way: Itamae's variables(...) arrive as instance variables, sadame's locals: as a Hash.

examples/migrate-from-itamae/itamae/templates/app.conf.erb
# Managed by Itamae.
server {
  listen 8080;
  server_name <%= @server_name %>;
  root /srv/app;
  # workers: <%= @workers %>
}
examples/migrate-from-itamae/templates/app.conf.erb
# Managed by sadame.
server {
  listen 8080;
  server_name <%= locals[:server_name] %>;
  root /srv/app;
  # workers: <%= locals[:workers] %>
}

What is identical

  • Recipes are Ruby files evaluated in order; resources run in the order they are declared.
  • package, directory, file, user, service, group, link and git keep their names.
  • include_recipe "path", relative to the including file, each file once.
  • notifies and subscribes, with delayed notifications by default and :immediately.
  • only_if and not_if with shell commands, on exec.
  • Templates are ERB with <%= %>, <% %> and -%>.

What changed

Itamae sadame
template "/path" do source "x.erb"; variables(a: 1) end file "/path", template: "x.erb", locals: { a: 1 }
remote_file "/path" do source "files/x" end file "/path", source: "x" (looked up next to the recipe, then in files/)
execute "name" do command "..." end exec "name", command: "..."
service "x" do action [:enable, :start] end service "x", enable: true, state: :running
link "/srv/app/current" do to "/srv/app/v2" end link "/srv/app/current", to: "/srv/app/v2"
git "/srv/app" do repository "URL"; revision "v2" end git "/srv/app", repository: "URL", revision: "v2"
action :nothing action: :nothing, on exec only; service without enable:/state: manages nothing
node[:attr], -j node.json, -y node.yml vars[:attr], vars.rb, --var, vars in hosts.rb
host inventory in node: node[:cpu][:total], node[:memory][:total], node[:hostname] facts[:cpus], facts[:memory_mb], facts[:hostname]
@name in templates locals[:name]; vars[:name] is also available
notifies :reload, "service[x]" notifies: [:reload, "service[x]"]
only_if / not_if on any resource on exec only
define plain Ruby methods in the recipe
run_command in recipes not available; recipes do not run commands while loading
mode "644" mode: "644" or "0644"; numbers are rejected
itamae local recipe.rb sadame apply recipe.rb
itamae ssh -h host recipe.rb sadame apply recipe.rb -h host (copies itself; nothing to install on the host)
--dry-run sadame plan
no drift command sadame drift

mitamae users: sadame is also a single binary built with mruby, so the mruby limits on recipe code are similar (no gems, no standard library); see Architecture. The differences from mitamae are the same as above, plus push over SSH, inventories and drift.

Resources Itamae has and sadame does not

http_request, local_ruby_block, gem_package and plugins. Use exec with a guard in their place:

Itamae sadame
http_request "/srv/app/example.html" do url "https://..." end exec "curl -fsSL -o /srv/app/example.html https://...", creates: "/srv/app/example.html"
gem_package "bundler" exec "gem install bundler", not_if: "gem list -i bundler"

link, group and git came in sadame 0.2.0; before that, they needed a guarded exec too.

How to port

  1. Move node.json or node.yml values into vars.rb (a Ruby Hash).
  2. Rewrite each resource block as a call with keyword options, using the table above. sadame plan rejects misspelled options with the list of valid ones.
  3. Change @name in templates to locals[:name] or vars[:name].
  4. Move only_if and not_if from non-exec resources into Ruby if around the resource, or into an exec.
  5. Run sadame plan on a machine Itamae already manages. A complete translation shows no changes, or only the differences you expect.

Common mistakes

  • Expecting @variables in templates. They are nil in sadame's templates; use locals[:name].
  • source paths. Itamae paths are relative to the recipe; so are sadame's, but sadame also looks in templates/ and files/, so "templates/x.erb" and "x.erb" both work.
  • Guards on files and packages. Only exec takes only_if and not_if; wrap other resources in Ruby if.

See also