Feature Friday #33: Why associative arrays when data containers exist?

Posted by Nick Anderson
October 25, 2024

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:

R: JSON representation of a_email (associateve array):
{
  "john@example.com": {
    "FirstName": "John",
    "LastName": "Doe"
  }
}
R: JSON representation of d_email (data container):
{
  "john@example.com": {
    "FirstName": "John",
    "LastName": "Doe"
  }
}

While their JSON representations are identical, the actually differ in a significant way. Notice that the associative array is defined key value pair by key value pair:

vars:
  "a_email[john@example.com][FirstName]" string => "John";
  "a_email[john@example.com][LastName]" string => "Doe";

Indeed, associative arrays in CFEngine are a collection of individual variables that are associated by their name, while a data container is an entire single unit.

"d_email" data => '{ "john@example.com": { "FirstName": "John", "LastName": "Doe" } }';

Both can be used and referenced similarly, but associative arrays being a collection of individual variables can be constructed dynamically. This can be a very useful capability, especially combined with CFEngine’s implicit list iteration.

Say we want to have a data structure representing files and we want to have a key with the files mtime, something like this:

{
    "/tmp/file-1.txt": { "mtime": "1234" },
    "/tmp/file-2.txt": { "mtime": "4321" }
}

This is trivial to achieve leveraging an associative array:

bundle agent __main__
{
  vars:
      "files" slist => { "/tmp/file-1.txt", "/tmp/file-2.txt" };
      "file[$(files)][mtime]"
        string => filestat( "$(files)", "mtime" );
  reports:
      "$(with)" with => storejson( file );
}
R: {
  "/tmp/file-1.txt": {
    "mtime": "1709940438"
  },
  "/tmp/file-2.txt": {
    "mtime": "1709940820"
  }
}

But that is much more challenging to do using data containers since the entire data container must be defined at once.

Happy Friday! 🎉