Puppet is essentially executable documentation, and should be written like documentation, not like a programming language. Puppet code should read like documentation. It's about making decisions on what should be in your infrastructure, documenting those decisions in Puppet, and then letting Puppet enforce them.
I’ve noticed a long trend of Puppet users writing manifests as if they’re programming. While the Puppet language (a domain-specific language, or DSL) has programming constructs such as conditionals, simple data structures, and loops, it lends itself far more to reading as documentation than as a program.
The benefits of writing Puppet manifests as documentation are enormous. Your manifests become vastly easier to maintain over time. It’s easier to understand what a system does, which is helpful when monitoring alerts are going off. Further, it’s easy for non-admins such as managers or security engineers to understand how a system is managed, which can save you endless time you'd otherwise lose to interruptions and answering questions.
Here are a few things to keep in mind while writing Puppet manifests:
Use the Roles and Profiles Pattern
The roles and profiles pattern is a way of expressing business logic in Puppet. Roles are the purpose of a node. They are the reason for the node’s existence. For example, a role may be the application or service a node provides. Profiles are the various components that make up a role. Examples are Tomcat, Apache, MySQL, etc. The roles and profiles pattern allows anyone to look at the configuration of a node at multiple levels of detail, with each level perfectly describing the next.
A great place to start learning about roles and profiles is the documentation.
Don’t Re-Invent Bash in Puppet
If you have a series of exec resources chained together, you’re Doing It Wrong. You may be used to thinking of things as iterative steps — and that’s thinking like a programmer, instead of like a documenter. Sure, sometimes you have to shell out. However, this should be an incredibly rare exception. Try to think about the final state of all the necessary pieces first, then write the resources (files, services, etc) that define those pieces. Also, browse the Puppet Forge to make sure someone hasn’t already written a module that solves your problem.
Side note: Don’t ever copy a file in an exec resource. Use a file resource instead.
Write Custom Functions
Many times, you might find yourself writing nested conditionals or chained if/else blocks to figure out what the value of a variable should be. It’s especially gruesome if you’re doing it for values of a hash or an array.
Don’t be afraid to write a custom function for that logic. Also, check to make sure the function doesn’t already exist in the stdlib module. For example, the
pick function in stdlib allows you take a series of variables and select the first one that has a valid value. The
strip function in stdlib will take an array of strings and remove any leading and trailing spaces from each string.
Separate Data From Logic
Most of the time, roles will have variations from system to system. For example, a Wordpress node may use a different database in staging than in production. All the other logic is the same, so it doesn’t make sense to have two Puppet classes for each variation. Use class parameters to feed these variations into the class so the logic remains identical for each instance of the role. Using Hiera with data bindings or setting class parameters in the Puppet Enterprise console make documenting these variations trivial.