There's enough for everyone

गते गते पारगते पारसंगते बोधि स्वाहा गते गते पारगते पारसंगते बोधि स्वाहा

YAML databags in chef

This post started as a grumpy tweet:

yaml is easy to type and easy to read. json is fiddly and fussy. So why is json in so many places where humans have to edit it?


For example, have you ever tried to put a ssh private key in a json file? (Let’s not, for now, debate the security implications of that, OK?)

Anyway, a couple of reasons that I can think of, in no particular order:

  • JSON is popular (fashionable, even), thanks to Couch, Mongo, etc, and node.js and AJAX
  • JSON is faster to parse and generate, because it’s simpler
  • some people say that the quotes and braces give them a feeling of security. That makes sense. Historically it’s children (ie non-expert users of a language) who usually drive the regularisation of natural language grammar.

There’s always tension between user-easy and developer-hard. (Nevermind that the users in this case are developers. Just not the developers.) YAML is harder for computers to parse and generate because it’s easier for humans, with our advanced context-dependent grammar parsing abilities.

But since converting from YAML to JSON is really trivial (as @chrismcg pointed out):

1
YAML.load_file('your_file.yml').to_json

there’s really no excuse for using json in places where humans have to edit it and there’s no pressing need for blinding speed. Like in chef databag definitions in your cookbooks.

But then inspiration struck, nigh unto that which striketh a man as he standeth in the shower. Rake tasks, similar to Ruby classes, are open – you can add dependencies to them later. Oh happy day!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
rule '.json' => ['.yml'] do |target|
  puts "converting #{target.source} to #{target.name}"

  hash = YAML.load_file(target.source)

  # get file name parts excluding the last extension
  parts = File.basename(target.name).split('.')
  parts.pop

  # add the default id key if necessary
  unless hash['id']
    puts "adding key #{parts.join('.')}"
    hash['id'] = parts.join('.')
  end

  # write to json
  File.write( target.name, hash.to_json )
end

yaml_data_bags = FileList.new("data_bags/**/*yml")
json_data_bags = yaml_data_bags.map{|yml_bag_file| yml_bag_file.gsub('yml','json') }

namespace :databag do
  task :upload_all => json_data_bags.to_a
  task :upload, [:databag] => json_data_bags.to_a
end

For the record, there’s also a knife plugin that does something very similar (I haven’t tried it): Knife plugin to create data bags from YAML files — Gist

Comments