Feature Friday #11: namespaces

Posted by Nick Anderson
May 24, 2024

Did you know that CFEngine has namespaces? Let’s see how they can facilitate policy sharing while avoiding “duplicate definitions of bundle” errors.

Most of the Masterfiles Policy Framework (MPF) and policy examples for CFEngine use the default namespace. However, body file control allows you to specify a namespace that applies for the rest of the file or until it’s set again by another body file control.

Let’s consider a contrived example. Say we have two policy files (policy-1.cf, policy-2.cf) for different services. In each policy file, we want to have a bundle where we store settings related to that policy. Traditionally this would be handled by using some bundle naming convention, so we might have bundle agent policy_1_settings and bundle agent policy_2_settings. Using namespaces you can keep your bundle names brief and use different namespaces to avoid “duplicate definitions of bundle” errors.

Let’s take a look at the implementation we might have for policy-1.cf.

/tmp/feature-friday-11/policy-1.cf
body file control
{
  namespace => "policy_1";
}

bundle agent settings
# @brief This policy is disabled by default
{
  vars:
    "state"
      string => "disabled",
      if => not( isvariable( "state" ) );

  reports:
    "In $(this.namespace):$(this.bundle): state = $(state)";
}

And similar for policy-2.cf:

/tmp/feature-friday-11/policy-2.cf
body file control
{
  namespace => "policy_2";
}

bundle agent settings
# @brief This policy is enabled by default.
{
  vars:
    "state"
      string => "enabled",
      if => not( isvariable( "state" ) );

  reports:
    "In $(this.namespace):$(this.bundle): state = $(state)";
}

Our policy entry then might look like this:

/tmp/feature-friday-11/promises.cf
body common control
{
  inputs => { "policy-1.cf", "policy-2.cf" };
  bundlesequence => { "go" };
}

bundle agent go
{
  methods:
    "policy_1:settings";
    "policy_2:settings";

  reports:
    "State of policy 1 = $(policy_1:settings.state)";
    "State of policy 2 = $(policy_2:settings.state)";
}

Executing the policy we can see our variable values in the different namespaces:

command
cf-agent -Kf /tmp/feature-friday-11/promises.cf
output
R: In policy_1:settings: state = disabled
R: In policy_2:settings: state = enabled
R: State of policy 1 = disabled
R: State of policy 2 = enabled

The most common gotcha when using namespaces is forgetting to specify the default namespace when using a body from the standard library.

For example, if we wanted to use body perms o from the standard library to specify the owner of a file, we would write it as perms => default:o( "owner" ):

/tmp/feature-friday-11/policy-2.cf
body file control
{
  namespace => "policy_2";
}

bundle agent settings
# @brief This policy is enabled by default.
{
  vars:
    "state"
      string => "enabled",
      if => not( isvariable( "state" ) );

  files:
    "/etc/hosts"
      perms => default:o( "root" );

  reports:
    "In $(this.namespace):$(this.bundle): state = $(state)";
}

Happy Friday! 🎉