Puppet’s Ruby DSL

In addition to the external Puppet DSL, Puppet also has an internal Ruby DSL available. The Ruby DSL has been in Puppet since 2006 but hasn’t been extensively used by anyone in the community. With version 2.6.x support was added to extend Ruby DSL and allow for extra syntactical sugar to deal with the convergence of Ruby and Puppet DSL. This page documents how you can leverage this DSL.

Warning: This documentation is based on the status of the Ruby DSL at version 2.6.4. Before commencing be warned that the API for Ruby DSL still may evolve, and some of the examples even show internal calls which should be avoided in production code. As the language evolves we will attempt to update this document to reflect any changes.

Examples of Ruby DSL

See some Puppet Ruby DSL examples on Github for code snippets that show off the different features discussed in this article.

Further references

Other implementations

Also available is Shadow Puppet – which is RailsMachine’s implementation of a Ruby DSL for Puppet.

How to use Ruby DSL with your code

Puppet has support for importing and using Ruby based DSL code with similar semantics as Puppet DSL. In fact, in many cases you can use the two interchangeably as long as they are kept in different files.

As you will see with the following sections the parser within Puppet will always acknowledge content accessed with an rb suffix as Ruby DSL and a pp suffix as Puppet DSL and will parse them accordingly.

Basic imports

Normally in your manifests you will import other Puppet DSL content using this methodology:

import 'foo.pp'
import 'bar.pp'

You can now instead pull in Ruby DSL using the same syntax but with the rb extension:

import 'foo.rb'
import 'bar.rb'

Modules and Ruby DSL manifests

When including a class:

include 'foo'
include 'bar::baz'

Would normally (respectively) include the puppet manifests files:

$PUPPET_ENV_DIR/modules/foo/manifests/init.pp
$PUPPET_ENV_DIR/modules/bar/manifests/baz.pp

To replace this behavior with Ruby DSL, simply interchange the pp files with rb based ones:

$PUPPET_ENV_DIR/modules/foo/manifests/init.rb
$PUPPET_ENV_DIR/modules/bar/manifests/baz.rb

Site or entry-point manifest

Ordinarily your Puppet DSL content is initially loaded through your site.pp file. This entire file can now be replaced with Ruby DSL.

First adjust your puppet.conf [main] block’s entry as follows to point to a ruby file:

[main]
  manifest=/etc/puppet/manifests/site.rb

If you are using environments, change the entry in each environments block entry as well.

[development]
  manifest=/etc/puppet/development/manifests/site.rb

Now using the above example configuration place the file here:

/etc/puppet/development/manifests/site.rb

And your initial content will now use Ruby DSL instead.

Ruby DSL reference

This section tries to go through the available Puppet language constructs and provide equivalent Ruby DSL forms for them. As we have already warned about, not all of these are going to be supported in the future.

Note: Top scope is not available in Ruby DSL at this point in time. Where this applies I have made sure there is a note (much like this one) indicating so. Ensure examples of this kind are put between a node, class or resource declaration. For example:

node "default" do
  scope.set
end

Resources

Defining Resources

You can define a resource this way:

define "foo", :port, :protocol => 'tcp' do 
  ... 
  # And then access instance vars this way: 
  ... 
  foo = @name 
  bar = @port 
  prot = @protocol 
  ... 
end 

Defining Defaults

Note: Not available at top scope.

You can define defaults for resources this way:

scope.setdefaults(:type_name,  
  [ 
    Puppet::Parser::Resource::Param.new(:name => "paramname1", :value => "paramvalue1"), 
    Puppet::Parser::Resource::Param.new(:name => "paramname2", :value => "paramvalue2") 
  ] 
) 

Using Resources

Note: Not available at top scope.

Using Normal Resources

You can call them using the convenience method:

foo "namevar", :port => 12, :protocol => 'tcp' 

For relationships use this:

foo "namevar", :port => 12, :protocol => 'tcp', :require => "Package[foo]"

Note: Don’t use quotation marks around qualified names in relationships, write :require => “Class[foo::bar]” instead of :require => “Class[‘foo::bar’]”.

You can declare an resource array similar to puppet manifest:

file { ['/tmp/a', '/tmp/a/b', '/tmp/a/b/c']:
  ensure => directory,
}

Ruby DSL:

file [‘/tmp/a’,‘/tmp/a/b’,‘/tmp/a/b/c’],
 :ensure => :directory

If there is a namespace clash with the name of the resource with something else in the ruby runtime (such as exec resource) you can do it like this:

create_resource :foo, "namevar", :port => 12, :protocol => 'tcp' 

Exporting and Collecting Resources

You can export resources like this:

export do 
  foo "bar", baz => "waz"
end 

And collect resources like this:

scope.compiler.add_collection(
  Puppet::Parser::Collector.new(scope, "Foo", nil, nil, :exported)
)

Virtualizing and Realizing Resources

You can virtualize a resource like this:

virtual do 
  foo "bar", baz => "waz"
end 

And realize a resource like this:

call_function(:realize,
 'Foo[bar]')
scope.compiler.add_collection(
  Puppet::Parser::Collector.new(scope, "Foo", nil, nil, :virtual)
)

Classes

Non-Parameterized Classes

Defining Classes

You define resources this way:

hostclass :fooclass do 
  ... 
end 

Using Classes

Note: Not available at top scope.

You can use a class this way:

include "class" 

There is no hostclass convenience method yet and using ‘class’ would clash with a ruby in-built keyword. Instead you can use:

create_resource :class, "fooclass" 

Just like with resources.

Parameterized Classes

Defining Classes

You define resources this way:

hostclass :fooclass, :arguments => {"myparam1" => nil, "myparam2" => AST::String.new(:value => "somedefault") } do 
  ... 
end 

Using Classes

Note: Not available at top scope.

You must use create_resources to do this:

create_resource :class, "fooclass", :myparam2 => "foo"

Nodes

Defining Nodes

node 'mynode' do 
  ... 
end 

Using Nodes

Simply run it as the node in question.

Functions

Defining Functions

Functions are already written in ruby. See the article: Writing Your Own Functions.

Using Functions

Note: Not available at top scope.

You can call functions like below. Make sure parameters are passed as an array.

notice ["param1"] 

If there is a namespace clash you can call functions this way:

call_function "notice", ["params1"] 

Vars

Note: Not available at top scope.

Defining Vars

Basic:

scope.setvar("name_of_var", "value") 

Appending:

scope.setvar("name_of_var", "value", :append => true) 

Unsetting:

scope.unsetvar("name_of_var") 

Using Vars

The functions available to access variables within ruby are much more extensive then using the Puppet DSL>

To access a single variable:

name_of_var = scope.lookupvar("name_of_var") 

To access a hash of all variables:

all_vars = scope.to_hash() 

Relationships

Defining Relationships with attributes

Defining resource relationships can be done using the following attribute based syntax, similar to the Puppet DSL:

foo "foo", :require => [ "Bar[bar]", "Bar[another_bar]" ]

Chaining Resources

TODO