Feature Friday #42: ob-cfengine3

Posted by Nick Anderson
December 27, 2024

For the final post in the Feature Friday series I am here to tell you about something I use nearly hourly, ob-cfengine3 which extends Emacs Org Babel for executing CFEngine policy.

ob-cfengine3 has been around for a little over seven years now and it has saved me countless hours, seconds at a time. At it’s core it let’s you type a snippet of policy and execute it directly in your document, sort of like Jupyter.

By default it tries to include the standard library during execution so that you don’t have to manually in-line the common things for small examples. For this to work you will want to be sure and setup CFEngine for unprivileged execution which I talked about in Feature Friday #25: Unprivileged execution.

It’s easy to …

Specify the bundlesequence

#+BEGIN_SRC cfengine3 :bundlesequence one,two
  bundle agent one
  {
    reports:
      "Running bundle $(this.bundle)";
  }
  bundle agent two
  {

    reports:
      "Running bundle $(this.bundle)";
  }
#+END_SRC

#+RESULTS:
: R: Running bundle one
: R: Running bundle two

Define additional classes

#+BEGIN_SRC cfengine3 :define EXTRA,MORE
  bundle agent main
  {
    reports:
      EXTRA::
       "EXTRA Class defined";
      MORE::
        "EVEN MORE";
  }
#+END_SRC

#+RESULTS:
: R: EXTRA Class defined
: R: EVEN MORE

Specify additional arbitrary options to cf-agent

#+BEGIN_SRC cfengine3 :extra-opts --show-evaluated-vars=default:main
  bundle agent main
  {
    vars:
        "test" string => "test string";
  }
#+END_SRC

#+RESULTS:
: Variable name                            Variable value                                               Meta tags
: default:main.test                        test string                                                  source=promise
: default:maintain_key_values_meta.tags     {"deprecated=3.6.0","deprecation-reason=Generic reimplementation","replaced-by=set_line_based"} source=promise

Change the output log level

#+BEGIN_SRC cfengine3 :log-level info
  bundle agent example
  {
     commands:
       "/bin/echo Hello World";
  }
  bundle agent __main__
  {
    methods:
        "example";
  }
#+END_SRC

#+RESULTS:
:     info: Executing 'no timeout' ... '/bin/echo Hello World'
:   notice: Q: ".../bin/echo Hello": Hello World
:     info: Last 1 quoted lines were generated by promiser '/bin/echo Hello World'
:     info: Completed execution of '/bin/echo Hello World'

Use a custom command for snippet execution

Here we are executing cf-agent from a docker image, this can be very useful if you are wanting to run a snippet against differing versions of CFEngine.

#+begin_src cfengine3 :command "docker run -v /tmp:/tmp zzamboni/cf-agent" :tmpdir /tmp
  bundle agent main
  {
    reports:
        "My hostname: $(sys.fqhost)";
  }
#+end_src

#+RESULTS:
: R: My hostname: 5cd98f9265a8

Include command in the output

Especially when showing examples or working on documentation showing the command executed can be quite nice.

#+begin_src cfengine3 :command "docker run -v /tmp:/tmp zzamboni/cf-agent" :tmpdir /tmp :command-in-result t
  bundle agent main
  {
    reports:
        "Hello world!";
  }
#+end_src

#+RESULTS:
: # docker run -v /tmp:/tmp zzamboni/cf-agent --no-lock --file /tmp/cfengine3-b7caCd
: R: Hello world!

Fun fact: This is how examples are handled in Learning CFEngine.

Execute partial snippets

#+begin_src cfengine3 :run-with-main yes
  reports:
    "Hello world!";
#+end_src

#+RESULTS:
: R: Hello world!

Getting it

In order to use this wonderful tool you will have to use Emacs. Learning Emacs might seem as steep a curve as CFEngine but I found Spacemacs got me going quickly and I didn’t have to leave any of my vi muscle memory behind. In fact, The agent is in - Episode 12 - Spacemacs for CFEngine covered getting set up.

Thanks

Thanks for coming along on this 42 part series as I celebrated CFEngine’s 30th anniversary, I hope you found some useful tidbits along the way.

One last time …

Happy Friday! 🎉

Checkout the rest of the posts in the series.