Personal EDN Ansible Playbooks
Peek
Here’s how most of the tasks look like:
[{:name "hi"
:hosts ["localhost"]
:vars {:user "_____"
:home "/home/"}
:tasks
[{:file {:path "/"
:state "directory"}
:loop [".local/bin"
".config/fish/functions"
".local/share/qutebrowser/userscripts"]}
...
Using multiline strings is straightforward, despite not being the prettiest thing I’ve seen:
{:name "Sway environment variables"
:blockinfile {:path "/etc/environment"
:block "# https://wiki.archlinux.org/index.php/Wayland#Qt_5
export QT_QPA_PLATFORMTHEME=qt5ct
# https://github.com/swaywm/sway/issues/595
export _JAVA_AWT_WM_NONREPARENTING=1"}}
Rationale
Nothing special, it’s just fun to write in Clojure :) I do generally dislike YAML because of its extreme, uncalled-for minimalism. It may or may not lead to me shooting myself in the foot (it does).
If anything goes south, I can always resort back to it, anyways:
cat playbook.edn | bb '(-> *in* slurp edn/read-string (yaml/generate-string :dumper-options {:flow-style :block}))'
Setup
Since every Ansible command I run requires a compilation step, I’ve written a short Babashka script to act as a middleman on each Ansible run. Luckily, Babashka comes with YAML (and obviously EDN) support built-in.
#!/bin/env bb
(import [java.io File])
(require '[clojure.core.match :refer [match]])
(require '[babashka.process :refer [process check]])
(defn edn->yaml
[file-path]
(-> file-path
slurp
edn/read-string
(yaml/generate-string :dumper-options {:flow-style :block})))
(defn main
[action edn-playbook extra-args]
(let [exec (format "ansible-%s" action)
yaml-file (.getAbsolutePath (File/createTempFile "playbook" ".yaml"))
cmd (into [exec yaml-file] extra-args)]
(spit yaml-file (edn->yaml edn-playbook))
@(process cmd {:out :inherit})))
(match (vec *command-line-args*)
[action playbook & args] (main action playbook args)
[action playbook] (main action playbook [])
:else (println "Usage: ednsible ACTION PLAYBOOK [EXTRA_ARGS]"))
nil
I saved the script as ednsible
and created these two aliases to avoid unfortunate scenarios:
alias ednsible-playbook="ednsible playbook"
alias ednsible-lint="ednsible lint"
Why convert to YAML instead of JSON?
An alternative path would be to generate JSON instead of YAML, since JSON is supposedly a subset of YAML 1.2. However:
ansible-lint
doesn’t work at all when given a JSON playbook, even when given a JSON playbook that works perfectly withansible-playbook
- Seems like the subsettery topic is controversial. With my luck, I’m sure I’ll step on a mine in this area; So I’d rather take the path of least resistance as YAML support is baked into Babashka :)
- The YAML parser that comes with Babashka is clj-yaml, which is a wrapper around SnakeYAML, which is a 1.1 parser 🙃