Writing Great Modules: An Introduction

We're taking a brief break from your regularly scheduled module of the week goodness with a mini series to discuss some of the elements that go into writing great Puppet modules. I'll try to sum it up in a few sentences:

Your module should be immediately useful to me after I install it. Documentation should be complete and the code itself should adhere to the community style guide. If I need to modify the behavior of your module, you should provide me an interface to do it so I don't have to modify your modules manifests. If I contribute features or bug fixes, I expect you to explain the process and collaborate with me on improving the module.

I'll be honest, writing a module isn't always an easy task, and all the things discussed here will add time and complexity to that module. This multi-part series on writing great modules is all about giving you the knowledge and tools to make your module more reusable, immediately useful to others and easier for your team and the community at large to maintain. My theory is that time spent initially building a more robust module will return you ten-fold the value over the life of that module in your environment.

With that in mind, I hope you find these concepts useful and return to the series next time as I dig deeper, leaving you to build your next great Puppet Forge module!

Before We Begin

If you're new to Puppet Modules or simply want a refresher, our incredible documentation team has recently created a page on Module Fundamentals.

De-Duplication and Collaboration

To borrow a phrase, “An hour in the Forge can save you days in the editor.” There are over 300 modules in the Puppet Forge now, covering any number of things. When you get that itch for your next Puppet module, try searching the Puppet Forge first. If you find something you like that suits your need, awesome! If you find something that is mostly what you want but need some features, consider contributing to the author's module instead of creating your own. Here are a couple of great docs to help you in consuming or publishing a Forge module:

Documentation

So you've written an awesome module that conjures unicorns from the cloud and you've published it to the Puppet Forge. But when I download it, I shouldn't have to dig into your manifest's directory and read the code just to figure out how it works. Documentation is key to quality modules that people will use.

There's really no secret sauce here, just write some text about your module, addressing the following questions:

  • What does your module do?
  • Does your module require anything else to function?
  • How should I declare your class(es)? Include example usage for each piece of functionality.
  • How can the user customize the module's behavior? (parameters, etc)
  • Where can the user turn for help?
  • Would you like contributors? What's the process?

At the very least, these questions should be addressed in the module's README file but it's also a good idea to place some appropriate documentation as comments above each class definition. Puppet ships with a subcommand called doc which will help you generate those commands into plain text (perhaps providing your README's content) or can even build a html-filled folder of rdoc goodness.

Take a look at Carl Caum's Bacula module for example. He clearly spells out what the module claims to do, what its requirements are and how to use it. If you take a look at this modules manifests, each has fairly robust comments in-line explaining usage and implementation decisions. Between the two, both users and contributors have most of the information they'll need.

Testing

It may sound obvious that you should test your module, but you really should. To start, this means making sure your module does what you claimed it can do in your documentation. This is commonly and pretty simply solved with smoke tests. As an overview, we have a great docs article on writing, running, and reading smoke tests.

On a basic level, this means you test your module's functionality against the operating systems you claim to support and include the puppet code you used to conduct your tests (usually class declarations) in the tests folder of your module. This convention allows consumers of your module to easily rerun the smoke tests you ran but also serve as great ways for someone to quickly try out its functionality. If a contributor to your module adds new functionality, make sure they include a smoke test for you to use.

Extra Credit: Smoke tests are great, but some members of the Puppet community are building automated tests for their modules. The big two I'm aware of are rspec-puppet and cucumber-puppet. These are advanced techniques that come with a steep learning curve but if you're curious, there's a recent blog post on test driven development. Once achieved, these tests make it easy to protect against regressions when merging new code. If you're investing heavily into Puppet, it may be worth your time to learn.

If you're particularly interested in rspec-puppet, the project’s author has an awesome tutorial on rspec-puppet.com.

Interfaces

When I download your module, I shouldn't have to modify your code to do something awesome with it. It's really tempting to code just for your environment and call it a day but a little extra effort will get you a module others can use and contribute to!

When you come across something that may need tuning depending on the user's situation, build a way for that user to modify your module's behavior without touching your manifests directory.

Let's say for example you have a motd module with a motd class for setting the contents of /etc/motd. You wrote the class to build /etc/motd from a template that includes your company's name in it. If I want to reuse your module, I'll have to modify your template or your manifest. This presents a challenge to reusability. Instead, a little extra Puppet code can provide me with a way to modify your module's behavior.

For example, provide two parameters (source & template) in a parameterized class. With a little bit of extra conditional logic and variables, users can specify their own template in the class declaration like this:

class { 'motd':
  template => template('motd/my_template.erb'),
}

All they'll need to do is drop their template into the templates folder or they could even specify a template from an entirely different location. As they're writing the template function, they can even concatenate multiple templates together!

Let's look an example of how this can be done.

# Static class with hard-coded template
class motd {
  
  file { '/etc/motd':
    ensure  => file,
    mode    => '0644',
    owner   => 'root',
    group   => 'root',
    content => template('motd/my_template.erb'),
  } 
  
} 
## Example declaration with the built-in template
include motd

## Or specify your own template
class { 'motd':
  template => template('mymodule/mytemplate.erb'),
}

## Even specify your own static file
class { 'motd':
  source => 'puppet:///modules/mymodule/myfile',
}

# Class with an interface for serving files vs templates
class motd (
  $template = '',
  $source = '',
) {

  # Fail gracefully if user tries to supply both source and template
  if $template and $source {
    fail('You cannot supply both template and source to the motd class')
  }

  # If nothing is specified, default to our template
  # If something was specfied, set the motd_* variable
  if $template == '' and $source == '' {
    $motd_template = template('motd/default_template.erb')
  } elsif $source != '' {
    $motd_source = $source
  } elsif $template != '' {
    $motd_template = $template
  } 
  
  file { '/etc/motd':
    ensure  => file,
    mode    => '0644',
    owner   => 'root',
    group   => 'root',
    content => $motd_template,
    source  => $motd_source,
  } 
  
} 

This is a fairly simple pattern but hopefully it makes sense. Again, we're just leveraging the tools in Puppet's language to make it easy for users of our module to use it in their environment without creating code they have to maintain themselves. Please invest some time thinking about this problem when you write your modules.

Versioning

A version number is required when you submit a module to the Forge. Version numbers can be tricky. In a place like the Puppet Forge, the versioning scheme you came up with for your modules may rock and make perfect sense to you and your team but may be completely different than the system I came up with for mine. How are you, as a module consumer, supposed to translate between the two and know at a glance whether functionality will fundamentally change between 1.0.1 and 1.1.0?

My recommendation is to follow the Semantic Versioning specification when you're publishing that next release. In the author's words: "Under this scheme, version numbers and the way they change convey meaning about the underlying code and what has been modified from one version to the next." We strive to follow this approach at Puppet Labs for modules we write and my hope is that each Forge user will as well.

Style

In the same way consistent versioning is important; consistent, clean and usable code is important. When you hear someone ask, "Does this module adhere to The Style Guide?", they are asking about the latter. We host a document on our website that we call the Style Guide. It's a collection of stylistic preferences and code structure best practices that was originally written to help our organization develop code that we could all easily read and develop in a uniform manner. It's even more appropriate for the large, distributed audience consuming and contributing modules to the Puppet Forge.

So read and adhere to the Style Guide. Do it for your teammates, do it for the Forge. To make that task easier, Tim Sharpe (@rodjek) maintains an awesome tool called puppet-lint. Like a dryer dutifully collects dead sock fibers, puppet-lint will scan your manifest code and alert you to certain violations against the Style Guide.

gem install puppet-lint
[root@centos6 modules]# cat helloworld/manifests/init.pp 
class helloworld {
  notify { 'example':
    message => "Hello World!",
  }
}
[root@centos6 modules]# puppet-lint helloworld/manifests/init.pp 
WARNING: double quoted string containing no variables on line 3

Puppet-lint doesn’t replace the Style Guide, so I encourage you to read it and keep it handy. You will find that it covers more than just the code styling that puppet-lint checks for. These are great tools to keep your code consistent, clean and consumable.

Publishing

Once you're finished writing your module, you'll want to publish it to the Puppet Forge. I'll cover that, more on these techniques and more advanced concepts next time. I'll also discuss updates to the Puppet Module tool which also now ships with Puppet Enterprise. If you'd like to whet your appetite, check out this quick Puppet Module Tool screencast created by one of the developers responsible for the tool updates.

Now What?

The Puppet Forge now has over three hundred modules helping sysadmins solve problems all over the map. You've got our Module of the Week blog series continuing to highlight great work and with Puppet Enterprise 2.5, you've got a greatly improved command-line Forge experience. Let's carry this momentum on to over six hundred awesome modules by PuppetConf 2012, and see you next time!

Additional Resources

Comments

Vamsee

Vamsee

Please fix the link to Part 2. It should be pointing to this: http://puppetlabs.com/blog/writing-great-modules-part-2/

Leave a comment