In the process, we remove our cucumber docstrings monkey patch. It does not work anymore and it was not a so good idea.
producer
Software provisioning and configuration management tool, providing a DSL to write "recipes".
Getting started
Installation (requires ruby and rubygems)
$ gem install producer-core
Simple recipe
Recipes are composed by tasks and a task includes actions. Here we
use the echo
action, which output the given string to standard
output. All the power of the Ruby language is available.
hello_message = 'hello world!'
task :hello_world do
echo hello_message
echo hello_message.upcase
end
$ producer simple_recipe.rb
hello world!
HELLO WORLD!
Shell command execution on remote host
The sh
action will execute a shell command given as a string on
the targeted remote host. The remote host can be specified with the
CLI option -t
.
task :show_zsh_pkg do
sh 'pkg info | grep zsh'
end
$ producer -t localhost show_zsh_pkg.rb
zsh-5.0.7 The Z shell
When execution fails, recipe processing is stopped and the action which triggered the failed execution is the last one to be applied.
task :sh_fail do
sh 'false'
echo 'end of recipe'
end
$ producer -t localhost sh_fail.rb
RemoteCommandExecutionError: false
$
Only the first action is applied.
Task conditions
A task can be bound to a condition: when the condition fails actions are skipped, otherwise actions are applied as usual.
This condition can be a simple ruby expression :
task :condition_pass do
condition { true }
echo 'will output'
end
task :condition_fail do
condition { false }
echo 'will NOT output'
end
Built-in tests
Specific test keywords are also available in the condition block
context, producer-core
ships with a few common tests,
producer-stdlib
provides more, and custom tests can be defined.
Here we use the sh
condition keyword which will pass when the
execution of the given shell command succeed, and fail when the
execution fails.
task :condition_sh_pass do
condition { sh 'true' }
echo 'will output'
end
task :condition_sh_fail do
condition { sh 'false' }
cho 'will NOT output'
end
Nested tasks
Complex tasks can be split into nested subtasks. Conditions have the same effect on tasks they have on actions, when the condition fails, subtasks of the current task are skipped.
task :main_task do
condition { true }
task(:foo_subtask) { echo 'do foo' }
task(:bar_subtask) { echo 'do bar' }
task(:baz_subtask) do
condition { false }
task(:baz_subtask_subtask) { echo 'do baz' }
end
end
$ producer nested_tasks.rb
do foo
do bar
Usage
Usage: producer [options] [recipes]
options:
-v, --verbose enable verbose mode
-d, --debug enable debug mode
-n, --dry-run enable dry run mode
-t, --target HOST target host
Actions
See: https://github.com/tjouan/producer-core/tree/master/features/actions
Tests
See: https://github.com/tjouan/producer-core/tree/master/features/tests
Templates
The following example can setup jails on a FreeBSD host.
In templates/freebsd/jail.conf.erb
:
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
exec.clean;
mount.devfs;
allow.chflags;
path = "/var/jails/$name";
<% @jails.each do |jail| -%>
<%= jail[:name] %> {
interface "<%= @if %>";
ip4.addr = <%= jail[:addr4] %>;
}
<% end -%>
Simple usage:
INTERFACE = 're0'.freeze
JAILS = [{
name: 'freebsd-10r1',
src: true,
addr4: '10.0.0.1'
}].freeze
task :jails_conf do
conf = template 'freebsd/jail.conf', if: INTERFACE, jails: JAILS
file_write_once '/etc/jail.conf', conf
end
Macros
FIXME
Test macros
FIXME
Macro composition
FIXME
Background
producer started as a collection of heterogeneous scripts (Ruby, POSIX shell, Perl…) in the late '90s. I wanted to experiment with the design and usage of Domain Specific Languages in Ruby, and refactor all my scripts as "recipes" in a common language.
Sample recipe
Based on the previous template example (FreeBSD jails.conf template):
require 'producer/stdlib'
JAILS_ROOT = '/var/jails'.freeze
ZROOT = 'tank/jails'.freeze
INTERFACE = 're0'.freeze
SETS = {
base: '2b028a894d25711ad496762622a52d74b1e32ee04693ad1cf056e3ddcdc23975',
src: 'f919287a5ef51d4f133f27c99c54f2e8054f408d3dd53bc60f4e233cc75ec03d'
}.freeze
JAILS = [
{
name: 'freebsd-10r1',
src: true,
addr4: '10.0.0.1'
},
{
name: 'freebsd-10r1-gcc',
src: true,
addr4: '10.0.0.2'
}
].freeze
task :freebsd_archives_fetch do
SETS.keys.each { |set| condition { no_file? "/tmp/#{set}.txz"} }
SETS.each do |set, sum|
sh <<-eoh
cd /tmp && \
fetch ftp://ftp.freebsd.org:/pub/FreeBSD/releases/amd64/10.1-RELEASE/#{set}.txz && \
sha256 -c #{sum} #{set}.txz
eoh
end
end
task :jails_fs_create do
condition { no_sh "zfs list #{ZROOT}" }
sh "zfs create -o mountpoint=#{JAILS_ROOT} -o compress=lz4 #{ZROOT}"
end
JAILS.each do |jail|
root = "#{JAILS_ROOT}/#{jail[:name]}"
fs = "#{ZROOT}/#{jail[:name]}"
task :jail_initialize do
condition { no_sh "zfs list #{fs}@install" }
task :jail_fs_create do
condition { no_sh "zfs list #{fs}" }
sh "zfs create #{fs}"
SETS.keys.each do |set|
next if set == 'src' && !jail[:src]
sh "tar -JxC #{root}/ -f /tmp/#{set}.txz"
end
end
task :rc_conf do
file_write_once "#{root}/etc/rc.conf", <<-eoh
hostname=#{jail[:name]}
# ...
eoh
end
task :root_passwd do
sh "chroot #{root} pw user mod root -w random"
end
task :mail_aliases do
condition { no_file? "#{root}/etc/mail/aliases.db" }
sh "chroot #{root} make -C /etc/mail aliases"
end
freebsd_update_patch_interactive "#{root}/usr/sbin/freebsd-update"
task :jail_snapshot_install do
sh "zfs snapshot #{fs}@install"
end
end
task :jail_update do
condition { no_sh "zfs list #{fs}@update" }
sh "chroot #{root} freebsd-update fetch install"
sh "zfs snapshot #{fs}@update"
end
end
task :jails_conf do
conf = template 'freebsd/jail.conf', if: INTERFACE, jails: JAILS
file_write_once '/etc/jail.conf', conf
end
Similar or related code and tools
Ruby DSL
- https://github.com/sprinkle-tool/sprinkle
- http://www.capistranorb.com/
- http://babushka.me/ (with BDD features, no network support?)
Ruby DSL, shell script transpilation
- http://nadarei.co/mina/ (Rake based DSL, requires and uses bash)
Ruby-like DSL
- http://puppetlabs.com/ (Ruby supported on >= 2.6.x)
Agents, daemons
- https://github.com/saltstack/salt (Python, YAML)
- http://www.cobblerd.org/ (Python, many features)
- https://www.getchef.com/chef/
SSH
- https://github.com/ansible/ansible (Python, YAML)
- http://docs.fabfile.org/ (Python)
- https://github.com/sebastien/cuisine (Python DSL, uses Fabric)
- https://github.com/kenn/sunzi (Ruby, provisioning, shell based)
- http://solutious.com/projects/rudy/ (Ruby, provisioning)
Ruby SSH related code
- https://github.com/leehambley/sshkit
- https://github.com/gammons/screwcap
- https://github.com/delano/rye/
- https://github.com/jheiss/sshwrap
BDD
- https://github.com/hedgehog/cuken (Cucumber)
- http://serverspec.org/ (RSpec, Net::SSH)
- https://github.com/auxesis/cucumber-nagios (Cucumber, Net::SSH, Webrat)
- http://larsyencken.github.io/marelle/ (Prolog, babushka inspired)