Do you enjoy escaping quotes inside strings? I sure don’t, and I really appreciate the flexibility CFEngine provides with 3 different quoting characters (", ', ` ). Let’s take a look.
This came up in the post show discussion for The agent is in, episode 39.
If you have a string that contains double quotes you might see it written with escaped quotes like this:
CFEngine works by defining a desired state for a given context and converging towards that goal. Given there is no fixed starting point and that the current context might change wildly it can be challenging to succinctly answer the question “What would CFEngine do?”.
In Feature Friday #22: Don’t fix, just warn we saw how an individual promise could be made to warn instead of trying to automatically converge towards the desired state, a granular --dry-run mode. This time, let’s take a look at the --simulate option of cf-agent.
Recently we had a Fireside Chat with long-time contributor and CFEngine Champion Bas Van der Vlies. During that talk he mentioned a Build module he developed: promise-type-docker-compose.
For this month’s Module Monday post I thought I would take this promise type for a spin alongside the Docker Compose Quickstart tutorial.
Setup For this blog I brought up a libvirt vagrant VM with Debian 12 and installed the latest LTS (3.24.0) with cf-remote. To install docker I follow the instructions at Install Docker Engine. I was using Debian 12 “bookworm” and found that the default packages docker.io and docker-compose are rather old and were not compatible with the promise-type-docker-compose module. Additionally, the module relies on the jq utility, so I install that as well. I may write policy some time to take care of these dependencies but especially the Docker Engine install involves quite a few steps so will leave that for another time.
Ever need to make a decision based on the version of something? The version_compare() function might be useful for you.1
Over time, software changes and features are added and removed. Sometimes, we need to make a decision based on versions. For example, the Include directive in ssh_config was introduced in OpenSSH 7.3.2 Let’s take a look at how we could possibly use it.
This example illustrates the basic use of version_compare():
Did you know CFEngine can self-organize hosts into different groups?
Say you have a few hosts that you want to reboot once a month. You don’t care when, but you want the hosts to self-organize and pick a date. The select_class attribute for classes type promises might be what you’re looking for. Let’s take a look.
We’ll keep things simple, so we want each host to self-select a day of the month (1-28).
What’s the difference between an associative array and a data container in CFEngine?
CFEngine has two ways in which structured data can be used, associative arrays (sometimes called classic arrays) and data containers. Let’s take a look at a simple data structure.
Here we have two data structures, a_email an associative array and d_email a data container. The policy emits the JSON representation of each.
bundle agent __main__ { vars: "a_email[john@example.com][FirstName]" string => "John"; "a_email[john@example.com][LastName]" string => "Doe"; "d_email" data => '{ "john@example.com": { "FirstName": "John", "LastName": "Doe" } }'; reports: "JSON representation of a_email (associateve array):$(const.n)$(with)" with => storejson( a_email ); "JSON representation of d_email (data container):$(const.n)$(with)" with => storejson( d_email ); } Looking at the output, they are identical:
Ever need to do some math during policy evaluation?
Sometimes configuration settings are based on available resources. For example, what if you want to calculate the size of shared buffers to be 25% of your available memory?
Let’s write some policy.
First, we need to figure out how much memory we have. Let’s parse this out from /proc/meminfo:
bundle agent __main__ { vars: "d_meminfo" data => data_readstringarray( "/proc/meminfo", "", "(:|\s+)", inf, inf); reports: "$(with)" with => storejson( "d_meminfo[MemTotal]" ); } R: [ "", "65505464", "kB" ] So, we have 65505464 kB of memory in total. Knowing that we can use eval() to calculate what 25% is.
Ever need to visualize the data your working with? storejson() to the rescue!
Let’s re-visit our example for sys.os_release from Feature Friday #12: Special variables:
bundle agent __main__ { reports: "My custom key 'NORTHERN_TECH_OWNER' contains $(sys.os_release[NORTHERN_TECH_OWNER])"; } R: My custom key 'NORTHERN_TECH_OWNER' contains Nick Anderson So, we saw the value of a single key, but if we don’t know what keys are available it can be useful to render the JSON representation. The with attribute in combination with storejson() provides a convenient way to visualize the JSON representation of structured data in CFEngine. Let’s adjust the policy:
Did you know you can use variables in class expressions?
If you are reading this, you probably are already familiar with the ability to use class expressions to restrict the context of multiple promises. For example, here we have three reports type promises, all guarded by the class expression linux::.
bundle agent __main__ { reports: linux:: "Only hosts with the linux class"; "Will have these promises"; "In context"; } And, if you are tracking this series, you know that you can restrict the context of a single promise using if or unless.1 However, you can also use variables in class expressions. Let’s take a look.
Class expressions are powerful. They let you restrict the context for multiple promises in a single statement. What if you want to further control the context of a specific promise?
Let’s take a look at a contrived example:
/tmp/feature-friday-28-0.cf bundle agent __main__ { reports: "I am running $(sys.os_release[PRETTY_NAME])"; linux:: "I love Linux!"; linux.ubuntu:: "Especially Ubuntu."; linux.redhat:: "Especially RedHat."; linux.!(ubuntu|redhat):: "But not RedHat or Ubuntu."; } command cf-agent -Kf /tmp/feature-friday-28-0.cf output R: I am running Ubuntu 22.04.4 LTS R: I love Linux! R: Especially Ubuntu. Here, we have a report showing the distribution we’re running through class expressions protecting the individual promises. We would see I love Linux! on Linux hosts. Depending on the specific distribution running we would see Especially Ubuntu. or Especially RedHat.. If we are running something other than those two distributions But not RedHat or Ubuntu. would be reported.