Puppet is written in Ruby, so it may sound fantastic news for power users that wish to extend Puppet capabilities. But there are some limitations that even a novice Puppet user can already face:
- Configuration is not written in Ruby, so you have to stick with the plain Puppet vocabulary. That's actually good to have a well-defined format that models the basic concepts like package, service, file, etc, and that's a guarantee to survive a Ruby upgrade or even a complete Puppet rewrite. But Puppet has reinvented variables for example, and without any object-oriented design. So you're stuck with some kind of artificial global variables following poorly-documented rules. So I would actually warn against using Puppet variables.
- Classes, as it's called in the Puppet semantic, are not classes like you would expect in the object-oriented sense. Classes in Puppet are just encapsulating other configuration bits. Using those classes simply consists in including their definition. Do not expect any sophisticated inheritance or any other OOP feature here. I would recommend to only use definitions, that are reusable wrappers with support for parameters and default values.
- Puppet templates (actually ERB templates) can reference Puppet variables defined in the configuration, but this is a hack, so don't expect sophisticated constructs here. Template writers don't have access to any high-level Puppet-oriented API. You can use plain Ruby constructs however, like string manipulation, math functions, etc. And the error reporting is so weak, see for yourself:
err: Could not retrieve catalog: Uncaught exception compile error
(erb):73: syntax error, unexpected kEND, expecting $end
end ; _erbout.concat "\n"
^ in method puppetmaster.getconfig
You have to restart puppetmasterd in debug mode and use erb -x -T '-' mytemplate.erb to find out what goes wrong!
- Documentation is written in a wiki. It's based on user contribution, so don't expect to receive more than you can give yourself. I suggest reading Pulling Strings with Puppet, although I haven't read it myself, but I hope it can offer some more in-depth understanding of Puppet. (On a side note, the Wiki is using a Trac plugin providing reStructuredText syntax, so make sure to preview your changes, it's not the Trac syntax).
Now let's look at the promise of extensibility.
Writing a custom function
That looks pretty seducing at a first glance, but it just doesn't work:
- All so-called functions are run on the server. It's a bit misleading, and I think it is a wrong design. Not being able to write a custom function that will be run on the client side does not make sense. When I'm writing bits of configuration, I'm communicating to Puppet the details that make sense on the local machine, I'm never referencing files that are local to the server! At least the Puppet syntax for a function call should reflect the fact that functions are actually parser functions, not general-purpose functions.
- Again, there is no OO design. For example, creating a function is achieved through calling Puppet::Parser::Functions::newfunction(:myfunction)
- As you can see in newfunction example above, Puppet internals make extensive use of Ruby symbols, which in my opinion reflects the absence of a real API, and the lack of a real internal design.
Also, note that the Ruby code has to be deployed both on the server and on the client. The fileserver along with the pluginsync mechanism allows to distribute the code automatically on all clients, although I haven't tested this myself.
Writing a custom type
So, you want to write a custom thing that will be executed on the client? There is no choice, you need to write a custom type. But the problem here is that I won't be able to talk a lot about it, because after spending 6 hours trying to hack a custom type with a lot of copy paste, I'm not satisfied with the result, and although I'm both new to Ruby and Puppet-internals, I don't think anyone can actually write any useful custom thing with Puppet. Puppet really needs a true API, be it in Ruby or not. The fact that the manifests are written in an independent syntax offers the possibility to remain compatible in the eventuality of a complete rewrite of the Puppet API.
The word of the end
If you need to extend the core capabilities of Puppet, be warned! There is no reliable and documented way to achieve it. Puppet is a great tool to automate system deployment and maintenance, but still lacks a true API with clearly documented extension points. Stick with simple documented Puppet constructs, use the exec type with hardcoded paths if there is no other way, or better: prepare your data on the server-side beforehand outside the control of Puppet if you need something more elaborate. For example, to concatenate Apache htpasswd files, I ended up writing a two-line shell script in the post-update hook of my Git repository. Much simpler than trying to concatenate files with Puppet! Extending Puppet is far from being a trivial task, even for an expert programmer!