0

I keep getting bit by an annoying gotcha that happens when Windows developers check out cookbooks from my Git repo with Git's autocrlf set to true. When they run vagrant up to bring up a Linux VM, the cookbook files are mapped into the VM with CRLF line endings, which causes no end of obscure errors when the shell and other POSIX utilities try to operate on the (now invalid) template files that have been copied into the VM.

The fix for that is simple enough: re-clone the repository after changing the autocrlf setting to input or false.

My problem is that, when you have the wrong line endings, the only symptoms are errors in strange places that in no way point to there being a problem with line endings.

How can I have Chef† check for the wrong line endings in, say, the cookbook template files and throw an error if it finds one? I think a simple Ruby snippet that does an assertion on the line endings in a given file that I can put at the top of a recipe would work.

Note: in the particular case of my repo, the sequence of steps is:

  1. Developer checks out repo
  2. Developer runs vagrant up
  3. Vagrant kicks off a Chef run in the VM
  4. Finally, the repo's build script runs in the VM

† Or really anything else included in the repo

Community
  • 1
  • 1
Michael Kropat
  • 14,557
  • 12
  • 70
  • 91

2 Answers2

2

Rubocop can be used to enforce unix style line endings (among many other things).

For example (from the command line, within the guest):

gem install rubocop
rubocop --only Style/EndOfLine  # only check line endings

Or it could be done from within the context of chef itself with something like the following recipe:

chef_gem 'rubocop'

ruby_block 'check line endings' do
  block do
    # It's probably better to call rubo cop code directly, rather than
    # shelling out, but that can be an exercise for the reader ;-)
    rubocop_cmd = Mixlib::ShellOut.new(
      'rubocop --only Style/EndOfLine',
      :cwd => 'dir_to_check'
    )
    rubocop_cmd.run_command

    # Raise an exception if it didn't exit with 0.
    rubocop_cmd.error!
  end
end

The uncaught exception will cause the chef run to bail out.

Jon Burgess
  • 2,035
  • 2
  • 17
  • 27
  • +1 Good answer. In my case, I'm not sure I can apply this since the local build scripts run in the VM only after the Chef run has happened. Sorry for not including all the detail in the question originally. – Michael Kropat Jul 24 '15 at 13:08
  • Whether it's running within the context of chef or not, e.g. after, doesn't really matter. As long as you have ruby/bundler available, the above will work. – Jon Burgess Jul 26 '15 at 00:16
  • Having said that, I'd recommend running something like this *before* DOS line endings are make it in to the master branch (or whatever) in scm. Otherwise it'll just be noise in there. – Jon Burgess Jul 26 '15 at 00:17
  • All good points, but to explain my situation a little better: part of why I'm using Vagrant is so that the developer doesn't need ruby/bundler on the host machine -- all they need to do is `vagrant up` and the required dependencies and build processes happen inside the VM. I completely agree about DOS line endings, but here's the crazy thing: msysgit on Windows defaults to `autocrlf` = `true`, which means even if the files in the repo have unix line endings, they will get checked out on to the host machine with DOS line endings. – Michael Kropat Jul 26 '15 at 15:49
  • I'm still not really sure I understand your use case properly. I've updated my answer, to be clear that the command line example can be run from within the guest VM, but also to include how the same thing could be run from within chef. – Jon Burgess Jul 27 '15 at 00:32
  • Makes sense. The `chef_gem` step was what I was missing conceptually. I finally tried implementing this, but it doesn't seem to work because `rubocop` doesn't want to check `.erb` files (my template files). Not part of the original question, but now I'd also like to check cookbook files, and I don't think rubocop would work with them either. Thanks for the idea though. – Michael Kropat Sep 11 '15 at 17:20
0

Here's a Ruby snippet that you can put in any Chef recipe:

def cookbook_supporting_files(*cookbooks)
  cookbooks = cookbooks.map {|name| run_context.cookbook_collection[name]}

  cookbooks.flat_map do |cb|
    (cb.manifest[:files] + cb.manifest[:templates]) \
      .map {|f| ::File.join(cb.root_dir, f['path']) }
  end
end

def dos_eol?(f)
  ::File.open(f, 'rb').read(4096).include? "\r\n"
end

cookbook_supporting_files(cookbook_name).each do |f|
  if dos_eol? f
    raise "Cookbook template '#{f}' contains CRLF line endings"
  end
end

This will check the line endings of the cookbook files and templates that exist in whatever cookbook the above snippet is placed in.

If you want to have the same snippet check other cookbooks, simply replace:

cookbook_supporting_files(cookbook_name)

With the list of the cookbooks you want to check:

cookbook_supporting_files('some_cookbook', 'another_cookbook')
Michael Kropat
  • 14,557
  • 12
  • 70
  • 91