Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
498 lines (406 sloc) 16.6 KB

Org Babel functions for CFEngine 3

https://melpa.org/packages/ob-cfengine3-badge.svg

./media/ob-cfengine3-demo-2019-08-01.gif

Table of contents

Usage

Respect promise locks

The --no-lock argument is passed by default. To use locks set the header argument use-locks to t.

#+BEGIN_SRC cfengine3 :use-locks t
  bundle agent main
  {
    reports:

      "My Content";
  }
#+END_SRC

Don’t automatically include the stdlib

The standard library is included by default via body file control. To avoid this define set include-stdlib to nil for example:

#+BEGIN_SRC cfengine3 :include-stdlib nil
  bundle agent main
  {
    reports:

      "My Content"
        printfile => cat($(this.promise_filename));
  }
#+END_SRC

#+RESULTS:
#+begin_example
  /home/nickanderson/org/cfengine3-15841vLB:6:0: error: Undefined body cat with type printfile
     error: Policy failed validation with command '"/home/nickanderson/.cfagent/bin/cf-promises" -c "/home/nickanderson/org/cfengine3-15841vLB"'
     error: CFEngine was not able to get confirmation of promises from cf-promises, so going to failsafe
     error: CFEngine failsafe.cf: /home/nickanderson/.cfagent/inputs /home/nickanderson/.cfagent/inputs/failsafe.cf
     error: No suitable server found
     error: No suitable server found
  R: Built-in failsafe policy triggered
    notice: Q: ".../cf-agent" -f /":    error: No suitable server found
  Q: ".../cf-agent" -f /":    error: No suitable server found
  Q: ".../cf-agent" -f /":    error: No suitable server found
#+end_example
#+BEGIN_SRC cfengine3 :include-stdlib t
  bundle agent main
  {
    reports:

      "My Content"
        printfile => cat($(this.promise_filename));
  }
#+END_SRC

#+RESULTS:
: R: My Content
: R: body file control{ inputs => { '$(sys.libdir)/stdlib.cf' };}
: R: bundle agent main
: R: {
: R:   reports:
: R:
: R:     "My Content"
: R:       printfile => cat($(this.promise_filename));
: R: }

Specify the bundlesequence

You can specify the bundlesequence by setting the bundlesequence header. For example:

#+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

You can define additional classes by setting the define header.

#+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 options to cf-agent

You can use the :extra-opts header argument to specify additional arbitrary options to pass to cf-agent. The string is passed as-is.

#+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

By default the agent is silent about repairs and only emits output on promises that are not kept and promises that explicitly output information (like a reports type promise).

Run with specified log level

CFEngine 3.12.0 and newer can use the singular –log-level option to set the log level to error, warning, notice, info, verbose, or debug.

NOTE: Setting log-level to debug will not enable all log-modules which is done when the debug boolean option is defined. To get as much information from the agent run as possible, use the =debug= header argument.

#+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'

Run with inform level output

#+BEGIN_SRC cfengine3 :info t
  bundle agent main
  {
    commands:
      "/bin/true";
  }
#+END_SRC

#+RESULTS:
  :     info: Executing 'no timeout' ... '/bin/true'
  :     info: Completed execution of '/bin/true'

Run with verbose level output

#+BEGIN_SRC cfengine3 :verbose t
  bundle agent main
  {
    commands:
      "/bin/true";
  }
#+END_SRC

#+RESULTS:
#+begin_example
   verbose: Could not open extension plugin 'cfengine-enterprise.so' from '/home/nickanderson/.cfagent/lib/cfengine-enterprise.so': (not installed)
   verbose: Successfully opened extension plugin 'cfengine-enterprise.so' from '/var/cfengine/lib/cfengine-enterprise.so'
   verbose: Successfully loaded extension plugin 'cfengine-enterprise.so'
   verbose:  CFEngine Core 3.11.0
   verbose: ----------------------------------------------------------------
   verbose:  Initialization preamble
   verbose: ----------------------------------------------------------------
  ... snipped for brevity
   verbose: No lock purging scheduled
   verbose: Outcome of version (not specified) (agent-0): Promises observed - Total promise compliance: 86% kept, 14% repaired, 0% not kept (out of 7 events). User promise compliance: 86% kept, 14% repaired, 0% not kept (out of 7 events). CFEngine system compliance: 0% kept, 0% repaired, 0% not kept (out of 0 events).
#+end_example

Run with debug level output

#+BEGIN_SRC cfengine3 :debug t
  bundle agent main
  {
    commands:
      "/bin/true";
  }
#+END_SRC

#+RESULTS:
#+begin_example
   debug: Trying to shlib_open extension plugin 'cfengine-enterprise.so' from '/home/nickanderson/.cfagent/lib/cfengine-enterprise.so'
   debug: Could not open shared library: No such file or directory
 verbose: Could not open extension plugin 'cfengine-enterprise.so' from '/home/nickanderson/.cfagent/lib/cfengine-enterprise.so': (not installed)
   debug: Trying to shlib_open extension plugin 'cfengine-enterprise.so' from '/var/cfengine/lib/cfengine-enterprise.so'
   debug: Could not open shared library: No such file or directory
 verbose: Could not open extension plugin 'cfengine-enterprise.so' from '/var/cfengine/lib/cfengine-enterprise.so': (not installed)
   debug: Setting hard class: default:debug_mode
   debug: Setting hard class: default:opt_debug
   debug: Setting hard class: default:verbose_mode
   debug: Setting hard class: default:inform_mode
 verbose:  CFEngine Core 3.14.0a.ed0158a8e
#+end_example

Tangled SRC blocks

By default, cfengine3 SRC blocks are not tangled. To tangle the blocks to a file, use the :tangle header argument.

When a file is tangled, a shebang is automatically added to allow for easy execution. To change or disable the shebang use the :shebang header argument.

#+BEGIN_SRC cfengine3 :shebang :tangle /tmp/example.cf
  bundle agent example
  {
     reports:
       "CFEngine $(sys.cf_version)";
  }
  bundle agent __main__
  {
    methods:
        "example";
  }
#+END_SRC
#+BEGIN_SRC cfengine3 :shebang /var/cfengine-3.7/bin/cf-agent -f- :tangle /tmp/example.cf
  bundle agent example
  {
     reports:
       "CFEngine $(sys.cf_version)";
  }
  bundle agent __main__

    methods:
        "example";
  }
#+END_SRC

By default, a body file control to include the standard lib via $(sys.libdir) is included in exported files. This facilitates small snippets of policy and does not interfere with a literate style of policy maintenance.

#+BEGIN_SRC term
  ~ $ cat /tmp/example.cf
  #!/var/cfengine/bin/cf-agent -f-
  body file control
  {
        inputs => { '$(sys.libdir)/stdlib.cf' };
  }

  bundle agent example
  {
      reports:
       "CFEngine $(sys.cf_version)";
  }
  bundle agent __main__
  {
    methods:
        "example";
  }
#+END_SRC

By default tangled files are saved so that only the owner can read, write and execute (700). Use the :tangle-mode header argument to override the default. NOTE: This differs from typical CFEngine policy permissions (600).

#+BEGIN_SRC cfengine3 :tangle /tmp/example.cf :tangle-mode (identity #o600)
  bundle agent example
  {
     reports:
       "CFEngine $(sys.cf_version)";
  }
  bundle agent __main__

    methods:
        "example";
  }
#+END_SRC

Why does ob-cfengine3 add a shebang and set the tangled file to executable by default?

The authors workflow consists of many small examples and snippets that are handed to many other people of varying organizations. Also, it’s a nifty trick when combined with library __main__ bundles for running partial sets of policy directly.

Why doesn’t ob-cfengine3 add a =bundle agent __main__= to the end of exported files?

It’s better suited for a snippet. It’s not useful unless it’s customized for the policy file.

#+BEGIN_SRC term
  ~ $ ./example.cf
  R: CFEngine 3.13.0
     error: Method 'example' failed in some repairs
  ~ $ ./example.cf -I
      info: Can't stat file '/tmp/./example.cf.missing' on 'localhost' in files.copy_from promise
  R: CFEngine 3.13.0
     error: Method 'example' failed in some repairs
#+END_SRC

Note that you can use the :tangle-in-main or :auto-main options to automatically wrap the snippet in a __main__ bundle. See Automatically wrap code in a __main__ bundle for details.

Use a custom command for snippet execution

By default, CFEngine code in a SRC block is executed using the cf-agent command. This can be changed using the command header argument. The command specified must accept the same arguments as cf-agent.

For example, if you have a Docker image called which executes cf-agent as the container’s ENTRYPOINT (see zzamboni/cf-agent for an example), you can have your code executed inside the container. Note that you have to take care that the path to which the temporary file is written by Emacs exists in the container as well. In this example, we specify the tmpdir header argument to specify that the temporary file is written to /tmp, and use the -v option in docker run to make the /tmp directory visible within the container.

#+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

Normally, the RESULTS block only contains any output produced by the execution of the CFEngine code. You can include the command in the output by specifying the command-in-result header argument:

#+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!

The command is shown exactly as used by ob-cfengine3, which may depend on the header arguments you use. You can use the following additional arguments to customize the displayed command. Note that these options are purely cosmetic.

  • command-in-result-prompt specifies the prompt to show before the command. Default is =”# “=.
  • command-in-result-command specifies the command name to show. Defaults to the value of command (default cf-agent).
  • command-in-result-filename specifies the filename to show. Defaults to the tangle filename if :tangle is specified, otherwise to a temporary file path generated by Emacs.
  • command-in-result-multiline specifies whether to show the command split across multiple lines. Its value can b e true, false or auto. Default value is false. In the case of auto, the determination is made depending on whether the command shown would be longer than command-in-result-maxlen.
  • command-in-result-maxlen specifies the maximum length before splitting the command if command-in-result-multiline is given as auto. Default is 80.

You can use these options to “prettify” the command shown, for example (contrast with the previous example):

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

#+RESULTS:
: > cf-agent --no-lock --file hello-world.cf
: R: Hello world!

Automatically wrap code in a __main__ bundle

Sometimes you may want to include in the document only the actual code to execute, without the surrounding bundle declaration, but still have it execute or tangle correctly. To control this, you have the following three header arguments:

  • :run-with-main automatically wraps the contents of the SRC block in a bundle agent __main__ before executing it;
  • :tangle-with-main automatically wraps the contents of the SRC block in a bundle agent __main__ before tangling it;
  • :auto-main is a shortcut to enable both :run-with-main and :tangle-with-main.
#+begin_src cfengine3 :run-with-main yes :tangle-with-main yes :tangle /tmp/hello.cf
  reports:
    "Hello world!";
#+end_src

#+RESULTS:
: R: Hello world!
cat /tmp/hello.cf
#!/var/cfengine/bin/cf-agent -f-
bundle agent __main__
{
reports:
  "Hello world!";
}

You can customize the template used to wrap the code in a bundle by setting the value of the ob-cfengine3-wrap-with-main-template variable. Its default value is =”bundle agent __main__\n{\n%s\n}\n”=.

Installation

From source

Clone the repo, add it to your load path

(add-to-list 'load-path "~/src/ob-cfengine3/")
(require 'ob-cfengine3)

Using package

(ob-cfengine3 :repo "nickanderson/ob-cfengine3" :fetcher github)
You can’t perform that action at this time.