Executing CFEngine SRC blocks in Emacs/Spacemacs Orgmode

Posted by Nick Anderson
September 12, 2016

A few months ago I posted a link on the help list to the CFEngine layer for spacemacs. Since then I have learned there are a few other org-mode users so I wanted to share how I got cfengine3 src blocks execution working. I added the following to my dotspacemacs/user-init.

(defcustom org-babel-cfengine3-command "/var/cfengine/bin/cf-agent"
  "Name of command to use for executing CFEngine policy.")

(defvar org-babel-cfengine3-command-options "--no-lock"
  "Option string that should be passed to the agent. Note that
  --file will be appended to the options.")

(defvar org-babel-cfengine3-file-control-stdlib "body file control{ inputs => { '$(sys.libdir)/stdlib.cf' };}\n"
  "File control body to include the standard library from
  $(sys.libdir). It is useful to inject into an example source
  block before execution so that bundles and bodies from the
  standard library are automatically available.")

(defun org-babel-execute:cfengine3 (body params)
  "Actuate a block of CFEngine 3 policy.
  This function is called by `org-babel-execute-src-block'.

  A temporary file is constructed containing
  `org-babel-cfengine3-file-control-stdlib and the body of the src
  block. `org-babel-cfengine3-command' is used to execute the
  temporary file."

    (let* ((temporary-file-directory ".")
    (tempfile (make-temp-file "cfengine3-")))
      (with-temp-file tempfile
        ;; TODO Consider making automatic stdlib inclusion optional
        (insert org-babel-cfengine3-file-control-stdlib)
        (insert body))
      (unwind-protect
      (shell-command-to-string
      (concat
        org-babel-cfengine3-command
        " "
        ;; TODO Consider adding a header option to specify bundlesequence
        org-babel-cfengine3-command-options
        " "
        (format " --file %s" tempfile)))
    (delete-file tempfile))))

Now any time I have a cfengine3 SRC block in org-mode I can simply run org-babel-execute-src-block ( CTRL-c CTRL-c in emacs/spacemacs documentation this is written as C-c C-c ) to have the block written to a temporary file, executed and the temporary file deleted. For example with my insertion placed inside the following SRC block.

#+Name: example policy
#+BEGIN_SRC cfengine3 :results raw :wrap EXAMPLE :exports both
  bundle agent main
  {
    reports:
     "$(sys.date)";
     "CFEngine $(sys.cf_version)";
     "Automatic inclusion of '$(sys.libdir)/stdlib.cf' when executed via org-babel-execute-src-block"
       classes => results("bundle", "example");

    vars:
     "c" slist => classesmatching("example_.*");

    reports:
     "class: '$(c)'";
  }
#+END_SRC

I type C-c C-c and my RESULTS are inserted.

#+RESULTS: example policy
#+BEGIN_EXAMPLE
R: Mon Sep 12 08:26:49 2016
R: CFEngine 3.9.0
R: Automatic inclusion of '/home/nickanderson/.cfagent/inputs/lib/stdlib.cf' when executed via org-babel-execute-src-block
R: class: 'example_kept'
R: class: 'example_reached'
#+END_EXAMPLE

Note that the standard library is automatically included by injecting a file control body into the temporary file before execution. For a block to be executed correctly it must contain either a body common control with the bundlesequence specified or include a bundle named main. Here you can see the exported results, now when I export my document I get a nicely formatted cfengine3 policy block along with the accompanying execution output.

bundle agent main
{
  reports:
   "$(sys.date)";
   "CFEngine $(sys.cf_version)";
   "Automatic inclusion of '$(sys.libdir)/stdlib.cf' when executed via org-babel-execute-src-block"
     classes => results("bundle", "example");

  vars:
   "c" slist => classesmatching("example_.*");

  reports:
   "class: '$(c)'";
}
R: Mon Sep 12 08:28:48 2016
R: CFEngine 3.9.0
R: Automatic inclusion of '/home/nickanderson/.cfagent/inputs/lib/stdlib.cf' when executed via org-babel-execute-src-block
R: class: 'example_kept'
R: class: 'example_reached'