Removing lint from the Puppet’s belly button of fluffy automation chaos, or: Using puppet-lint to save yourself from style faux pas
Wouldn’t it be nice if you never made a mistake or a typo again? Okay, that’s a slightly misleading offer. How about just never committing such gaffes in with your code? “How?” I hear you cry. With the illustrious Tim Sharpe’s puppet-lint and some version control derring-do!
Following on from Adrien’s wonderful post on syntax checking and writing tests, you have a way of checking the syntax and style guide from the command line. Using this approach, even on a mature (like a fine wine) Puppet code base, you can move towards having beautiful, aligned, ordered manifests—the type of manifests you’d be happy taking home to meet the family.
Puppet-lint takes the Puppet Labs Style Guide and places it nicely in a command line tool. Think of it as a programmatic PEP-8 for those from a Pythonic world. Now, where this comes in handy is when you’re using revision control (which is all the time, obviously!), because you can tie the two together. Yes, every time you try and commit your code to your RCS, it checks the files you’re committing, and makes sure you haven’t used a tab where there should be a space.
In Puppet Labs’ kickass Operations department, we’re ever so slightly keen on Git, not least due to the wonderful GitHub. RCSHub just doesn’t cut it for us these days.
With Git, as with many an RCS, you’re free to define hooks to do weird and wondrous things upon certain actions. This chapter on git hooks from Customizing Git explains them in detail. If you’re using Subversion, then this pre-commit documentation suggests what you can do on the server side to accomplish the same thing.
For this, the pre-commit hook is the one we want to utilise. I first make a bash script to get a list of the files that change in the commit. Then, the hook script goes through them one at a time, seeing if they’re Puppet manifests by name, and running puppet-lint on them as it goes. If any of the manifests fail linting, it exits there and then I may go fix them at my leisure!
#!/bin/bash # Requires bash, as it uses the [[ ]] syntax. # # If it's puppet code, lint it up. # I we don't have puppet-lint, so just exit and leave them be. which puppet-lint >/dev/null 2>&1 || exit # Variables goes hither declare -a FILES IFS=" " FILES=$(git diff --cached --name-only --diff-filter=ACM ) for file in ${FILES[@]} do if [[ $file =~ \.*.pp$ ]] then puppet-lint --with-filename "$file" RC=$? if [ $RC -ne 0 ] then exit $RC fi fi done exit 0 |
I save that file to .git/hooks/pre-commit in my repository, give it a light sprinkling of ‘chmod +x’, a dash of ‘gem install puppet-lint’ and I am ready to roll.
Exhibit A, the Larch
So let’s see this bad-boy in use, I hear you cry! Let’s take a manifest that breaks all of the rules, and see what happens…
node 'hubert.humphrey.edu' { user{ "mhunter": managehome => true, home => "/home/mhunter", comment => 'Mark Hunter', ensure => 'present' } } |
[enlil:puppetlabs-modules]% git commit hubert.pp hubert.pp - WARNING: double quoted string containing no variables on line 3 hubert.pp - WARNING: double quoted string containing no variables on line 5 hubert.pp - WARNING: => on line isn't properly aligned for resource on line 5 hubert.pp - WARNING: => on line isn't properly aligned for resource on line 6 hubert.pp - WARNING: => on line isn't properly aligned for resource on line 7 hubert.pp - ERROR: two-space soft tabs not used on line 4 hubert.pp - ERROR: two-space soft tabs not used on line 5 hubert.pp - ERROR: two-space soft tabs not used on line 6 hubert.pp - ERROR: two-space soft tabs not used on line 7 hubert.pp - ERROR: two-space soft tabs not used on line 8 hubert.pp - ERROR: trailing whitespace found on line 2 hubert.pp - WARNING: ensure found on line but it's not the first attribute on line 7 |
Woah, that’s a lot of errors. By default, puppet-lint will let warnings through, but stop dead on errors, and we got them all! I’ll tidy this manifest up…
node 'hubert.humphrey.edu' { user{ 'mhunter': ensure => present, managehome => true, home => '/home/mhunter', comment => 'Mark Hunter', } } |
Much more readable, and now when I try and commit it, I get:
[enlil:puppetlabs-modules]% git commit hubert.pp [master 6020331] Add in a new pupil to the school! 1 file changed, 10 insertions(+) create mode 100644 hubert.pp |
Clean, and the best part is I don’t have to alter my workflow, I can just carry on editing with Vim, commit my work as normal, and just be kindly reminded when a manifest has things that need tidying up.
Learn More
9 Comments
Be aware that the script will take the file from the working copy rather than the Git index, which may differ from one another. You can use `git cat-file` to read off the index. Although puppet-lint doesn’t currently take STDIN so it would need to be written out to a tempfile.
I wrote a pre-commit hook script that runs puppet-lint, does a manifest validation test, and checks template files for syntax errors: https://github.com/jasonhancock/puppet-svn-hooks
too bad it doesn’t work with ruby 1.8.6: undefined method `start_with?’ for “methods”:String (NoMethodError)
Asq, Ruby 1.8.6 isn’t a supported platform for Puppet (http://docs.puppetlabs.com/guides/platforms.html#ruby-versions) as there’s just too many problems with it. I can imagine puppet-lint has similar issues.
When running the above pre-commit setup documented above on a Debian testing box I get the following:
/usr/lib/ruby/1.8/puppet/parser/lexer.rb:19:in `lex_error’: Could not match ~/ (Puppet::LexError)
from /usr/lib/ruby/1.8/puppet/parser/lexer.rb:453:in `scan’
from /usr/lib/ruby/1.8/puppet/parser/lexer.rb:304:in `fullscan’
from /usr/lib/ruby/vendor_ruby/puppet-lint/plugin.rb:59:in `run’
from /usr/lib/ruby/vendor_ruby/puppet-lint.rb:158:in `run’
from /usr/lib/ruby/vendor_ruby/puppet-lint.rb:157:in `each’
from /usr/lib/ruby/vendor_ruby/puppet-lint.rb:157:in `run’
from /usr/bin/puppet-lint:91
from /usr/bin/puppet-lint:88:in `each’
from /usr/bin/puppet-lint:88
Unfortuantely I don’t know ruby well enough to figure out the problem.
HenryB: That sounds like it’s trying to puppet-lint something that isn’t a puppet file perhaps? I updated the code example to be more stringent on what files it runs it on. Try updating it and trying it again.
That solved it. Thanks for the quick response.
Module of the Week: pdxcat/amanda - Advanced Network Backup | Puppet Labs
[...] Puppet Lint [...]
There is also a Puppet module for Guard which solves the similar task: https://github.com/alister/guard-puppet-lint