From free-for-all to coding-in-style: puppet-lint-check

Daniel Schutterop
07-06-2014

OK. puppet-lint is a very powerful tool, most of us know it and live by its rules.

When I started using puppet-lint, it made me feel a bit like my mother, telling me to clean my room. After cleaning up my recipe I treated myself to a nice pat on the back for doing the stuff I was postponing all along, the stuff I was supposed to do right in the first place.

For those who do not know puppet-lint; this gem checks if the puppet recipe (you pass as a parameter to puppet-lint) is in accordance with the puppet style-guide. No hard tabs (oh the hard tabs I removed…), no double quotes containing only a variable, etc. etc.

In all, a very friendly piece of equipment for those who write puppet code in a team and care for reading the recipes collegues write.

The nature of puppet-lint, telling what you do wrong, unfortunately doesn’t really appeal to many fellow sysadmins, most environments I get to visit use common sense (which inherits common error) to write recipes and live in a, well, free for all, any-style-goes-type-of-world.

Not that it’s a cardinal sin to tab your way through recipes. I mean, you can still read them, it’s just nicer to adhere to the same standards. It’s a matter of professionalism, if anything.

You know, if I need to edit your recipes and, heaven forbid, you need to edit mine on my day off, let’s make sure we’re not only speaking the same language, but the same dialect as well…

Anyway, getting a sysadmin to chance his routines is pretty easy, about as easy as turning water into wine; It can be done (or so we were told) but it’s easier to find a blue-striped unicorn dancing the Hokey Pokey in Central Park. Or so I’m told.

The easiest way to accomplish change in this matter is to explain the benefits (again and again) and provide the tooling to ease into a new way of thinking. That’s where I found my script to be of use. Where puppet-lint screams out the many, many errors in your recipes, which is good if you want to get down ‘n dirty to clean some code, puppet-lint-check only spits out a percentage (and a smiley) in red or green, based on:

  • The total number of lines in the recipe
  • The number of lines containing errors
  • A threshold you define, also known as ‘the suck-level’

It works pretty simple:

  • You input a file
  • It checks the number of lines in the recipe
  • It runs puppet-lint on the file and extracts the number of errors
  • It runs uniq (because you can have multiple errors on a line, it’s just not sexy to display values over 100%)
  • And finally displays a percentage (xx% bad)

Modes


Interactive mode

So, if you run a puppet-lint-check on /etc/puppet/modules/slartibartfast/manifests/init.pp, here’s what you would find on one of my machines:

[wormhole@puppet /]$ puppet-lint-check /etc/puppet/modules/slartibartfast/manifests/init.pp
🙂 init.pp 0% bad (threshold 5%) (:
[wormhole@puppet /]$

Or if it sucks:

[wormhole@puppet /]$ puppet-lint-check /etc/puppet/modules/slartibartfast/manifests/init.pp
🙁 init.pp 54% bad (threshold 5%) ):
[wormhole@puppet /]$

Auto mode

Auto mode checks your complete puppet module directory so you can see which manifests are ‘OK’.
The output looks a little like this:

[wormhole@puppet /]$ puppet-lint-check
🙂 slartibartfast->init.pp 0% bad (:
🙁 marvin->init.pp 80% bad ):
🙁 marvin->paranoid.pp 62% bad ):
🙂 arthur->init.pp 0% bad (:
🙂 arthur->vogonpoetry.pp 0% bad (:

🙂 3 recipes meet the bar
🙁 2 recipes are not up to standards
Remember: we're checking the recipes with an error threshold of 5% up.
meaning, recipes with more than 5% errors are marked 'bad', there may be multiple errors per line!
[wormhole@puppet /]$

puppet-lint-check Script

The script doesn’t do too much I must say, it’s little more than a wrapper for puppet-lint that leaves out all the things that should be done and is a little, how shall I put it.. A little less blunt. instead of delivering the harsh truth of where you failed, it basically gives a thumbs up (or down) when your recipe reaches a certain threshold of error.

Now you can say: ‘No, of course your puppet code doesn’t suck, 20 out of 100 recipes match the criteria’. To me, that sounds a lot nicer than ‘Dude, your code sucks’.

In short:

  • You define a threshold in the script (the maximal margin of error (or, sucklevel))
  • The puppet base dir (where your recipes live)
  • A puppet environment (in many puppet deployments there is a puppet-devel and puppet-production seperation)
  • The path to puppet-lint
#!/bin/bash
#
# Check for recipe style guide adherance
#
# This script has two operating modes:
#
# Interactive: ($0 ) will check the recipe file
# you pass as the first argument
#
# Automated mode: $0 will run in puppet_base (which contains the puppet environment $puppet_env)
# and will look in every subdirectory for recipe files
#
# The script requires puppet-lint!
#
# threshold=100
# Threshold we want:
# Percentage we need to be correct for a file not
# to display as output
#
# threshold=100 would allow recipes without errors to be suppressed
# threshold=90 would display every recipe with more than 10% errors
# Modify these vars if you like...
threshold=5
puppet_env='puppet-production'
puppet_base="/etc/puppet/${puppet_env}/modules"
puppet_lint=/usr/bin/puppet-lint
# Leave the stuff below alone.
failcount=0
passcount=0
if [ -x $puppet_lint ]; then
if [ -z $1 ]; then
for FILE in `ls $puppet_base`; do
if [ -d $puppet_base/$FILE ]; then
for RECIPE in `ls $puppet_base/$FILE/manifests/*.pp`; do
recipeFile=$RECIPE
if [ -f $recipeFile ]; then
recipeLines=`wc $recipeFile | awk '{print $1}'`
recipeLintLines=`$puppet_lint $recipeFile | awk '{print $NF}' | sort | uniq | wc -l | awk '{print $1}'`
lintPercentage=`echo "scale=1;${recipeLintLines} / ${recipeLines} * 100" | bc -l`
lintPercentageRound=`echo $lintPercentage | cut -d. -f1`
if (( $lintPercentageRound < $threshold )); then
echo -e "\e[00;32m 🙂 ${FILE}->`basename $RECIPE`\e[00;32m ${lintPercentageRound}% bad (:\e[00m"
let "passcount += 1"
else
echo -e "\e[00;31m 🙁 ${FILE}->`basename $RECIPE`\e[00;31m ${lintPercentageRound}% bad ):\e[00m"
let "failcount += 1"
fi
fi
done
fi
done
echo -e ''
echo -e "\e[00;32m 🙂 ${passcount} recipes meet the bar \e[00m"
echo -e "\e[00;31m 🙁 ${failcount} recipes are not up to standards \e[00m"
echo -e ''
echo -e "Remember: we're checking the recipes with an error threshold of ${threshold}% up."
echo -e "meaning, recipes with more than ${threshold}% errors are marked 'bad', there may be multiple errors per line!"
else
if [ -f $1 ]; then
recipeLines=`wc ${1} | awk '{print $1}'`
recipeLintLines=`$puppet_lint $1 | awk '{print $NF}' | sort | uniq | wc -l | awk '{print $1}'`
lintPercentage=`echo "scale=1;${recipeLintLines} / ${recipeLines} * 100" | bc -l`
lintPercentageRound=`echo $lintPercentage | cut -d. -f1`
if (( $lintPercentageRound < $threshold )); then
echo -e "\e[00;32m 🙂 `basename $1`\e[00;32m ${lintPercentageRound}% bad (threshold ${threshold}%) (:\e[00m"
let "passcount += 1"
else
echo -e "\e[00;31m 🙁 `basename $1`\e[00;31m ${lintPercentageRound}% bad (threshold ${threshold}%) ):\e[00m"
let "failcount += 1"
fi
else
echo -e "\e[00;31m 🙁 ${1} file doesn't exist \e[00m"
fi
fi
else
echo -e "\e[00;31m 🙁 ${puppet_lint} doesn't exist or is not executable \e[00m"
fi

It wouldn't be the first time I got some heat for publishing a script that's pre-alpha (which is often the state I leave things in), so if things don't work, if the script bugs out, just let me know 🙂

LEAVE A REPLY

you might also like