Home > Ramblings, Sysadminery, technology > puppet and vim

puppet and vim

I’ve been using vi/vim for nearly my entire professional life, and most of my computer life as well. I gave emacs an honest try for a couple of days a few years ago but just couldn’t grok the shortcuts and make it feel natural. Recently I overhauled my setup on my laptop and in specific tuned to to what I generally spend a lot of development time on… puppet.

First things first I turned my .vim directory into a git repo (published here http://github.com/stick/vimfiles). This makes it pretty easy for me to keep track of what changes I made and why, till you spend a few years with it there’s a bit of black magic in how the more advanced parts of vim work. So after I wholesale cleaned out my .vim directory I started essentially fresh.

First plugin I use is pathogen, it’s a must and the only plugin that actually resides in the normal places in vim’s rc directory. Pathogen allows you to define a bundle directory (~/.vim/bundles by default) and will load plugins in whole from that tree. That allows each plugin to have it’s own tree and you don’t get file collisions etc. It also makes it super easy to enable or disable a plugin.

I won’t go into super detail on all the vim’ness that I’ve setup most of it is from this great article. I’m not a pure developer so some of the things I don’t use but the plugins he recommends are spot on.

One of the core tenets in puppet is to not duplicate work, it even gets enforced to a certain degree with not being able to duplicate resources. When you combine this the the recommended module structure and correct use of the autoloader and you will find yourself creating a lot of manifest files. There are two things I use that help with this. One is a template plugin, the other is snipmate. The template plugin comes into play whenever I create a new file, I have a skeleton directory that contains stub for different filetypes. When I edit a new file it reads in that template, interpolates a few tokens and inserts it into my new file buffer. I always work on modules from the top of the module (makes moving easier and helps with the git side of things I think). So in my ssh module when I do:

$ vi manifests/server.pp

I get a file that looks like:

Edit a new manifest file

This file didn't exist prior to editing.

If you notice the header comment is mostly filled out, the class declaration is already set and a few other things. How does this work? Well the skeleton file has tokens in it that I have defined (called tags, which is a bit of an overload of the term). That tag can be a simple string or you can use vi’s scripting language to a certain extent to figure things out. In the case of the class declaration I know that I almost always work in the top dir of a module so I know what module I’m in from that. Then the autloader in puppet has very specific rules how how filenames translate to classes so I take advantage of that with a little substitute. The template plugin I use is eteSkeleton, I liked how relatively simple it was, however I did have to fork it and fix a few things with it. The original source is here, my fork is here and my specific tags file is here to see how I did the class logic.

I also use snipmate to create definitions, language constructs etc within manifests. Snipmate lets you type if and get a completed if statement that you can tab through the various elements of. Those familiar with TextMate will recognize it instantly. My snippets file for puppet is here, it was originally done by R.I. Pienaar but I modified it to match my personal syntax style etc.

Syntax highlighting is pretty simple, there’s a puppet.vim that’s included in the puppet source tree, it defines the groups and objects and how to highlight them, then you can control the colors through vim’s colorschemes. I don’t change my colors from the default, I just tweak my terminal settings to brighten up some of the colors (dark blue on black specifically).

ftdetect/puppet.vim handles file detection of manifest files, there’s a lot of things that are keyed off of set filetype=puppet, this just matches *.pp. There’s not really a case where I’m editing a manifest and it’s not a .pp so we don’t have to read lines or anything to determine filetype, if we did that would go here. ftplugin/puppet.vim contains any filetype specific settings I want for puppet.

kp=pi looks a little cryptic. kp is shorthand for kewordprg (or keyword program). This defines the program that vim will run when you press ‘K’ (note the capital) in normal mode. In stock vim if you try this on a word it will run man (cword is vim-speak for word under cursor). pi is actually shipped as part of puppet (at least it’s in the gem install) as an analog to ri (ruby documentation). It acts exactly the same as puppet describe. This means that in a manifest I can position the cursor on say an ‘exec’ resource that I’m writing and hit ‘K’ and it’ll shell out and run pi exec (puppet describe exec) and give me all the resource documentation for that version of puppet (assuming I’m on a box that has puppet installed). This is great when I can’t remember the exact attribute on a resource or similar. My only complaint is that pi doesn’t page when it returns a lot of text (see pi file) and it’s annoying to have to page up to start reading (I run 100% of the time in screen and have my pageup key mapped to copymode). But wait I have a solution. Vim always runs &kp so I can’t really set kp to something with a pipe and pager in it. If you try: :set kp="pi | less -F" you will end up running: pi exec |less -F execas kp always appends . So in my .bashrc I define the following:

function pi() {
  command pi "$@" | less -F

This defines a local bash function called pi which calls pi with whatever arguments were passed and pipes it to less -F (-F only paging if there’s more than 1 screen of text). The command construct in bash prevents a loop between the function and the actual command. It also allows the path to be used rather than hardcoding /usr/bin/pi which would have the same net effect. It’s a tad more portable. Now in vim when I cursor over a resource and hit ‘K’ I get nice puppet documentation without having to switch windows or anything. Be aware that since it’s a locally defined function you have to set set shellcmdflag=-ic in your .vimrc else when vim shells out the function won’t be defined. I could also make a shell command in my path somewhere and use that as that adding the ‘i’ flag to the shell causes it to bg vim in certain circumstances as it’s an interactive shell now.

The surround plugin is great for throwing quotes or curlys around things, since puppet uses two different syntaxes for variables when they are quoted vs bare, I use that alot to wrap a variable in curlys. The normal mapping (see surround’s documentation) to do that is: ysiw}. Cryptic and hard to remember? Yup. Basically it’s ‘ys’ (you surround); ‘iw’ (inner word, :h text-objects will change your life), ‘}’. The surround plugin uses the left brace to surround with spaces and the right brace to surround without. I map this to } to make it easy.

NERD_Commenter (a very awesome plugin to do filetype/language specific comments) uses a dictionary for filetypes to define additional comment characters.

let g:NERDCustomDelimiters = {
      \ 'puppet': { 'left': '#', 'leftAlt': '/*', 'rightAlt': '*/' }
      \ }

This defines the comment character (and alternate comment character) for puppet filetypes. Using NERD_Commenter you can switch between shell style and c-style comments. Do things like visually select a block and comment it one swoop, and uncomment sections really easily.

Puppet resources use fat comma’s to specify parameters. The style guide (and I tend to agree) states that all co-located fat commas should be aligned based on the longest parameter in the list. Fixing that is tedious and annoying. This is where the ‘Align’ plugin comes in handy. Align can do a million things to reformat text, but simply for this case I only use it for fat commas. I want to select a block of text with visual select mode (greatest thing in vim ever btw).
Then run :Align =>. This will align everything on those delimiters. I have this mapped to = in my .vimrc. I don’t use tabs so there might be additional settings to get it to align with tabs instead of spaces, but I think it honors expandtab and smarttab as needed.

I have ctags and taglist setup to integrate with puppet, but I’m finding myself not really using it much. I tend to think in the autoloaders terms and just open a new window/etc to what I need to look up rather than use vim to jump back and forth. If I could get it setup such that when I cursor over a statement like:
include ssh::serverand hit ] (which causes a taglookup); it would then take me to where class ssh::server was defined I would probably use it more, but since ‘::’ isn’t a part of a word boundary that doesn’t work. I got the ctags configuration from Nasrat and it works great, I just haven’t really figured out how to integrate it into my coding-workflow. If anyone has any more practical enhancements or advice leave them in the comments.

Syntax checking is a must. I’ve written a couple of shell scripts to wrap around puppet --parseonly to check a whole module, do erb checking etc etc. Then I found syntastic. Syntastic is simply put… fan-fucking-tastic. It supports doing syntax checking based on filetype so you can change things per language. Vim has always had the ‘make’ and ‘makeprg’ settings but syntastic wraps that up in a nice package and gives you a way to extend it. It also provides a function you can put in your statusline to alert you when you open (or save) a file that the file has syntax errors (and where they are).

A quick aside about the statusline. I used to hate it, this ugly white line at the bottom that broke up the visual and flow of the text and really didn’t contain anything useful. Then I discovered how to change it and fill it with all sorts of useful information. All of my statusline settings (with the exception of setting laststatus=2 in .vimrc) are found in statusline.vim.

The code found in syntax_checkers/puppet.vim defines the proper makeprg and does the other syntastic setup. The hard part is getting the errorformat set correctly. Vim often times bails back home to it’s ‘C’ roots and this is one of those times. errorformat (:h errorformat for in depth information) is a pattern that will be matched against the output of the makeprg (in this case puppet apply –parseonly … ). The difficulty is it’s not a regular expression, it’s a scanf expression. scanf is an older c routine for matching single or multiline text and it’s a bit cryptic and confusing. Give me a regex anyday, you combine that with how vim needs things escaped and it was a chore. But it’s done now and hopefully I’ve saved you some time. Puppets parse-only option it’s terribly smart. Once it finds a single error it will stop parsing. I talked to Luke about this and it’s a limitation of the DSL in ruby. The parser can’t really continue once it finds and error so it doesn’t have any way to report on all the errors. The net result is that if you sit down and bang out a manifest really fast then syntax check it, you’ll get the first error, fix it, save see the second, etc etc etc. Still nicer than having to quit, run puppet, edit, fix, and repeat. Be aware that forcing a syntax check each time the buffer is opened does slow vim down a bit (well not really vim, it’s just waiting on puppet), so if you notice it and it bothers you (it doesn’t me) look at syntastic documentation on how to disable it and only turn it on when you want it. I’m toying around with seeing if I can get syntastic to check against running puppet in noop mode to catch things like duplicate definitions and other things that parser doesn’t deal with, but there’s a lot to ignore and filter through and noop mode generates a lot of output since it’s not really changing anything. I’m also not sure if it’s all that useful at that phase, I generally do a lot of sandbox and vm testing anyway which I would still have to do. I’m sure if someone wanted to write a specific parser for manifests that was smarter than parseonly but friendlier than noop there would be beer and scotches all around, but I tend to think most of us are more interested in writing actual manifests than something to check manifests.

That’s all of my puppet specific vim setup, my entire vim tree is online at http://github.com/stick/vimfiles and is mostly documented as to what various things do. All the credit for the various plugins goes to their authors, the vim community is really good, just spend a bit getting to know vim before you jump in.

Happy puppeteering!

  1. December 5, 2013 at 6:39 PM

    Found this else where to help others looking for help with the ctags :: word boundary issue

    ” Puppet tag navigating fix
    au FileType puppet setlocal isk+=:

  1. December 5, 2011 at 7:55 AM

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: