Will your policy work? cf-promises can check the CFEngine policy for syntax errors and give you an overview of the host’s context.
It’s always a good idea to check your policy for syntax errors.
Consider this policy file:
/tmp/feature-friday-6.cf bundle agent feature_friday { reports: "$(this.promise_filename)" printfile => cat( "$(this.promise_filename)" ) } Can you spot the error? Let’s see if cf-promises can help:
command cf-promises -f /tmp/feature-friday-6.cf output /tmp/feature-friday-6.cf:6:2: error: syntax error } ^ /tmp/feature-friday-6.cf:6:2: error: Check previous line, Expected ';', got '}' } ^ error: There are syntax errors in policy files The output tells us that there is a syntax error near line 6, column 2. A semicolon (;) was expected but instead, a closing curly brace (}) was found. We are missing the semicolon that terminates the promise for bundle feature_friday_6 which is called as a methods promise.
Do you maintain multiple policy sets? Do you leverage policy written by others? Ever wished for an easier way to upgrade your policy framework? cfbs can help to improve all of these cases.
cfbs is a command line tool that aims to help simplify managing a policy set and working with CFEngine Build, a website for finding and sharing modules. A policy set usually - but not always - builds on top of some base, like the Masterfiles Policy Framework (MPF). Custom policy is added on top, and you’re off to the races. When a new version of CFEngine is released, the best practice is to upgrade the MPF (assuming you are using it) as the first step in the upgrade process. If you have not modified the MPF, re-integrating the custom policy on top of the new MPF is a relatively straightforward process. If you have modified the MPF it’s 1) something you need to know that you did and 2) a process of managing your diffs against the newer version of the MPF.
What’s the easiest way to install cfengine? Have you heard of cf-remote?
cf-remote was born out of a developer’s itch for an easy way to get CFEngine installed on some host for testing. We have featured cf-remote in several posts1 since it was first released in 2019, but today is Friday, so let’s review its features.
Overview cf-remote (available via the Python Package Index) primarily targets installing CFEngine on a remote host, but it also provides some related conveniences including:
When you want to inspect both the return code and output from a command execresult_as_data() might be the function you are searching for.
Most CFEngine policy writers have used execresult() and returnszero(). They are useful when you want to do something based on the output of a command or based on its successful execution (returning zero). For example:
/tmp/feature-friday-3.cf bundle agent __main__ { vars: "hostname" string => execresult( "$(paths.hostname)", "useshell" ); classes: "my_command_returned_zero" expression => returnszero( "$(paths.hostname)", "noshell" ); reports: "$(hostname)"; my_command_returned_zero:: "$(paths.hostname) returned 0"; } command cf-agent --no-lock --log-level info --file /tmp/feature-friday-3.cf output precision-5570 R: precision-5570 R: /bin/hostname returned 0 But, sometimes, you care about specific return codes. However, you might also want to consider the output from a command. execresult_as_data() facilitates inspecting both output and return code from a command’s execution, by returning a data structure. Let’s take a look.
Ever wanted to manipulate a string - temporarily - for an individual promise? Check out the with attribute and its special, $(with) variable.
Sometimes you need some variation on a string for a specific case. Traditionally, to achieve this you’d simply define another variable.
Here is a contrived example:
I have a string, nginx and I want to emit a report that contains both the string itself and the upper case version of the string.
Looking for a way to concisely set a variable conditionally? Have you heard of ifelse()?
In CFEngine, traditionally class expressions are used to constrain promises to different contexts. Setting a variable to different values based on context might look like this:
/tmp/feature-friday-1.cf bundle agent __main__ { vars: "MyVariable" string => "My Default value"; redhat_8|centos_8|rocky_8:: "MyVariable" string => "My value for EL 8"; ubuntu_22:: "MyVariable" string => "My value for Ubuntu 22"; any:: "MyVariable" string => "My value on Friday", if => "Friday"; reports: "It's $(sys.date) and I am running on $(sys.os_release[PRETTY_NAME])"; "MyVariable is '$(MyVariable)'"; } command cf-agent --no-lock --log-level info --file /tmp/feature-friday-1.cf output R: It's Mon Mar 11 12:36:41 2024 and I am running on Ubuntu 22.04.4 LTS R: MyVariable is 'My value for Ubuntu 22' That’s great, lots of flexibility, but with an increasing number of options the policy can get quite long and it’s easier for a human interpreter to lose track of the context. The same can be achieved in a single statement using ifelse().
Last year in commemoration of 30 years of CFEngine I received this lovely coin.
I thought it would be fun to celebrate by writing 42 blog posts, each with some little tip about CFEngine. So, be sure to come check in on Friday for the first post in the Feature Friday series.