People & Software

My ramblings on people using software

Puppet Custom Types, the Easy Way

devops, puppet, ruby

Robert scratched his head. How would he get a Puppet class to manage a complex resource on his systems? I guess I’ll have to make a Custom Type, he thought. But last time I looked into that, I noticed you need to know a lot about Puppet Internals.

If you recognize this thought process, maybe it’s time to meet easy_type. Like the name says, easy type is designed to make it easy to build a Custom Puppet Type. In this article, we will introduce easy_type. We do this by taking you along on in the process of making a Custom Type. In the process, we explain how easy_type actually makes it…… well easy to build a Custom Type. In the first part, we will show you how to get Puppet to see and index all your resources. In the next blog posts, we will enhance the Custom Type to be able to create, remove and modify existing resources.

This article is changed at the 28th of October to refelect changes in easy_type

When to build a Custom Type

Well that’s an interesting question! The Puppet language is actually very versatile, and you can do anything with it. But why would you step out of puppet and into ruby to build a Custom Type? In one of his excellent blog posts, Gary Larizza explains it in one sentence: “Because 20 execs in a defined type… “. Another indication, you’d be better of on a Custom Type path instead of a defined type, is when you need to build one or more custom facts.

What would drive someone to write a custom type and provider for Puppet anyhow? Afterall, you can do ANYTHING IMAGINABLE in the Puppet DSL*! After drawing back my sarcasm a bit, let me explain where the Puppet DSL tends to fall over and the idea of a custom type and provider starts becoming more than just an incredibly vivid dream:


  • You have more than a couple of exec statements in a single class/defined type that have multiple conditional properties like ‘onlyif’ and/or ‘unless’.

  • You need to use pure Ruby to manipulate data and parse it through a system binary

  • Your defined type has more conditional logic than your pre-nuptual agreement

  • Any combination of similar arguments related to the above


If the above sounds familiar to you, then you’re probably ready to build your own custom Puppet type and provider.

Gary Larizza in Fun With Puppet Providers

Let’s get started

To get started, you first need to install easy_type. You can either add it to your Puppetfile or use the puppet module installer to install it.

Install easy_type using puppet-librarian

To add it to your Puppetfile, you can add the following line:

1
mod "hajee/easy_type", :git => "git://github.com/hajee/easy_type.git"

After that run the librarian to add the right modules to your puppet tree:

1
librarian-puppet install

The librarian reads the Puppetfile and puts the nescecary files into your module tree. After this command, you can see easy_type in your list of modules.

Install using puppet module installer.

To install easy_type using the puppet installer, enter the next command:

1
$ puppet module install hajee/easy_type

Well that was easy. Now we have all the basic requirements in place to start. What better time then now to think about the resource you want to manage.

How to manage the resource?

Because easy_type hides some of the Puppet intricacies from you, you can focus on the resource. To continue, you need to know how:

  • get a list of all available resources?
  • what’s important in a resource?
  • to create a resource?
  • to modify an existing resource
  • to remove one

This is the basic information you need to build a custom resource.

An example please?

As an example I’ve picked the same Custom Type as is described in the book “Puppet Types and Providers”. This is an excellent book if you are into writing custom resources. It describes in great detail everything you must known and do to build a Custom Type in the standard puppet way. In the book they state:

the `custom_package` type is not intended to serve as a replacement of Puppet’s existing package type. It serves as an example of how to take advantage of the features of the type and provider APIs.

Puppet Types and Providers By Dan Bode Nan Liu

Of course, the same thing counts over here.

A scaffold

To get started, it would be helfull if we have a scafffold. Let’s say we want to name our own module my_own_easy_type and we name our own Custom Type custom_package. easy_type also contains some generators to make this easy. But first we have to create the directories and files needed for any puppet module:

1
$ puppet module generate robert-my_own_easy_type

Now we can start creating the custom type:

1
$ puppet type scaffold easy_type custom_package --force

It works…. Well, kind of

Actually this is all you have to do to get the basic Puppet stuff in order. Now you can give the puppet command to get a view of all available resources:

1
puppet resource custom_package

And it shows….. nothing. No output. But what is more import at this stage, is no error’s or warnings. Now we can start working on the thing we know best. How to get the information about our resource, a package in this example, out and manageable.

Let’s get the index of all packages

So let’s change the custom_package so we can use Puppet to get a list of all resources. To do this, we need to add just a couple of lines. Lets first look at the method to_get_raw_resources. Its function is to return an Array containing all available resources you manage. Every Array entry is preferably a Hash. A Hash that contains the all the individual properties and parameters of an instance of your resource.

In our example, we are managing packages. To get a list of packages on a Linux system, you can use the rpm command. The following os command returns a list of all packages formatted in a comma separated way containing its name and its version.

1
rpm -qa --qf '%{NAME}, %{VERSION}\n'

We would like the Custom Type to execute this command and convert the data to an Array of Hashes.

To do just that, we need to change the to_get_raw_resources method to:

1
2
3
4
to_get_raw_resources do
  packages_string = rpm('-qa','--qf','%{NAME}, %{VERSION}\n')
  convert_csv_data_to_hash(packages_string,['name', 'version'])
end

The packages_string will contain a string with all the information. The convert_csv_data_to_hash method will convert it to a hash. The elements in the hash are named name and version.

To let easy_type know rpm is an os command, we need to add the line:

1
set_command(:rpm)

TA TAAHH

Now when we ask Puppet for the available custom_packages, we get a list of all the available packages.

1
puppet resource custom_package

Output:

1
2
3
4
5
6
7
custom_package { 'GConf2':
  ensure  => 'present',
}
custom_package { 'MAKEDEV':
  ensure  => 'present',
}
...

But wait, didn’t we also collect the version information? What about that? To Add the version information, we need to add a version property. A property looks very similar to the parameter that was already available in the scaffold. Easy_type contains a generator to add a property. Let’s run it:

1
$ puppet type generate property version custom_package

Now when we ask Puppet for the available custom_packages again, we get a list of all the available packages, inclusing the version information.

1
puppet resource custom_package

Output:

1
2
3
4
5
6
7
8
9
custom_package { 'GConf2':
  ensure  => 'present',
  version => '2.14.0',
}
custom_package { 'MAKEDEV':
  ensure  => 'present',
  version => '3.23',
}
...

Next…

In the next blog post, we will enhance our work. We will add support for changing existing resources. If, in the meanwhile you want to check easy_type out. You can checkout the source in the github repository. You can also check some Oracle Custom Types which are build upon easy_type. We would love to hear your feedback on this work. You can find the example here

Since first publication, some changes where made to the code fragments in this blog post. If you used the code in this post, please check the changes in the github repo

Comments