Inspired by a Slack conversation today, I decided to evaluate moving my group’s Ansible playbooks to a modular format using either dhall and jsonnet. I had previously looked at dhall when I started moving our containerized services to kubernetes, but found the type system overbearning due to the complexity of k8s, and after leaning about Tanka my decision to use jsonnet was an easy one. however, the lure of type-safe config is a strong one…

I rewrote one of our simpler playbooks, which does rolling server updates and reboots, in both dhall and jsonnet. All sources are avaible on GitHub. The original YAML config, which runs a system update procedure, is in update_original.yaml. Jsonnet input is in update.jsonnet and output is in update_jsonnet.json. Dhall input in update.dhall and output is in update_dhall.yaml, and for sake of comparison with jsonnet update_jsonnet.json.

There are two differences betwen the dhall and jsonnet playbooks, both caused by the flexibility of Ansible allowing a field to be either a list of strings or just a string when there’s only one item (I’m not sure if that’s universally true or just for these options). It looks like dhall supports union types, so this could probably be handled in the type library.

Comparison

For a quick comparison, here are the “main” playbook files.

YAML

- hosts: all
  remote_user: root
  # do one host at a time rather than several in parallel
  serial: 1
  tasks:
  - name: renew kerberos ticket
    local_action: command kinit -R
  - name: declare downtime
    command: set_my_downtime
  - name: disable puppet
    command: puppet agent --disable 'kretzke disabling to do docker update'
  - name: remove yum version lock
    command: yum versionlock clear
  - name: yum update
    yum:
      name: '*'
      state: latest
  - name: enable puppet
    command: puppet agent --enable
  - name: run puppet
    command: puppet agent -t
    register: result
    until: result.rc == 0
    retries: 10
    delay: 60
  - name: reboot node
    reboot:
      # some nodes can take a _long_ time to reboot
      reboot_timeout: 3600

dhall

let Ansible =
    -- https://raw.githubusercontent.com/softwarefactory-project/dhall-ansible/master/package.dhall
      https://raw.githubusercontent.com/retzkek/dhall-ansible/master/package.dhall sha256:50595ec584149cfbdb8f763d629af0315c893f6937a4c2502b425e06372d44d9

let t = ./tasks/package.dhall

let user = env:USER as Text

in  [ Ansible.Play::{
      , hosts = "all"
      , remote_user = Some "root"
      , serial = Some [ "1" ]
      , tasks = Some
        [ t.sys.renewTicket
        , t.sys.declareDowntime
        , t.puppet.disable "${user} disabling to do docker update"
        , t.yum.removeVersionLock
        , t.yum.update
        , t.puppet.enable
        , t.puppet.run
        , t.sys.reboot
        ]
      }
    ]

jsonnet

local t = import 'tasks/package.libsonnet';

function(username='kretzke') [{
  hosts: 'all',
  remote_user: 'root',
  serial: 1,
  tasks: [
    t.sys.renewTicket,
    t.sys.declareDowntime,
    t.puppet.disable('%s disabling to do docker update' % username),
    t.yum.removeVersionLock,
    t.yum.update,
    t.puppet.enable,
    t.puppet.run,
    t.sys.reboot,
  ],
}]

Thoughts

Conclusion

Comparing dhall and jsonnet is like comparing haskell and ~python~ lisp: do you want the guarantees of type safety, or the productivity of a dynamic language? Either is better than plain YAML (or python, down with significant whitespace)!