Change in behavior: Renaming bundle agent main

Posted by Nick Anderson
April 11, 2022

A recent change in the Masterfiles Policy Framework (MPF) is renaming bundle agent main to bundle agent mpf_main.

What’s in a name: Source https://stijndewitt.files.wordpress.com/2019/11/whats-in-a-name-shakespeare.jpg

This change is intended to make it easier to run individual parts of your policy leveraging the library main bundle functionality (bundle agent __main__).

Library main bundles were first introduced in CFEngine 3.12.0. The functionality allows for the definition of bundle agent __main__. When this bundle definition is present in the policy entry (the first policy file that CFEngine reads) the bundle is understood to be used as the default bundlesequence.

This example illustrates the behavior:

bundle agent __main__
{
  reports:
    "Hello World, from $(sys.policy_entry_filename)";
}

When we run the policy without providing a bundlesequence (the --bundlesequence or -b option to cf-agent), and since body common control is absent, not defining bundlesequence, the __main__ bundle is used in the place of main.

cf-agent --no-lock --file /tmp/example-0.cf
R: Hello World, from /tmp/example-0.cf

We can see that if we instead define the bundle as main:

bundle agent main
{
  reports:
    "Hello World, from $(sys.policy_entry_filename)";
}

The behavior is identical when the policy is executed:

cf-agent --no-lock --file /tmp/example-1.cf
R: Hello World, from /tmp/example-1.cf

So, why are we renaming bundle agent main in services/main.cf to bundle agent mpf_main?

We are renaming it because you will get errors if you execute a policy entry that has bundle agent __main__ and there also exists bundle agent main within the inputs of that policy execution.

For example, if we place both bundle agent main and bundle agent __main__ in the same file:

bundle agent main
{
  reports:
    "Hello World, from $(sys.policy_entry_filename)";
}
bundle agent __main__
{
  reports:
    "Hello World, from $(sys.policy_entry_filename)";
}

When executing, we get an error about duplicate definition of bundle.

cf-agent --no-lock --file /tmp/example-2.cf | head -n 2
/tmp/example-2.cf:1:0: error: Duplicate definition of bundle main with type agent
/tmp/example-2.cf:6:0: error: Duplicate definition of bundle main with type agent
   error: There are syntax errors in policy files
   error: Policy failed validation with command '"/home/nickanderson/.cfagent/bin/cf-promises" -c "/tmp/example-2.cf"'

This error is emitted because bundle agent __main__ is internally translated to bundle agent main and since there already exists a bundle agent main it’s seen as a duplicate.

The same thing happens if there is bundle agent __main__ in the policy entry and bundle agent main in another file added to inputs. In this example, /tmp/example-3.cf (which has bundle agent __main__) loads /tmp/example-4.cf (which has bundle agent main) as part of it’s inputs.

body file control
{
  inputs => { "example-4.cf" };
}
bundle agent __main__
{
  reports:
    "Hello World, from $(sys.policy_entry_filename)";
}
bundle agent main
{
  reports:
    "Hello World, from $(sys.policy_entry_filename)";
}

Again, we see that running /tmp/example-3.cf as the policy entry results in duplicate definition of bundle errors:

cf-agent --no-lock --file /tmp/example-3.cf | head -n 2
/tmp/example-3.cf:5:0: error: Duplicate definition of bundle main with type agent
/tmp/example-4.cf:1:0: error: Duplicate definition of bundle main with type agent
   error: Policy failed validation with command '"/home/nickanderson/.cfagent/bin/cf-promises" -c "/tmp/example-3.cf"'
   error: CFEngine was not able to get confirmation of promises from cf-promises, so going to failsafe

Note, it’s not a problem in the other direction. If you have bundle agent main in your policy entry that loads another file to inputs which has bundle agent __main__, there is no error since bundle agent __main__ in the non policy entry file is not internally translated to bundle agent main.

In this example we reverse the previous example so that the policy entry file contains bundle agent main and bundle agent __main__ is present in a file loaded to inputs.

body file control
{
  inputs => { "example-5.cf" };
}
bundle agent main
{
  reports:
    "Hello World, from $(sys.policy_entry_filename)";
}
bundle agent __main__
{
  reports:
    "Hello World, from $(sys.policy_entry_filename)";
}

When executing we can see that no errors are emitted.

cf-agent --no-lock --file /tmp/example-5.cf
R: Hello World, from /tmp/example-5.cf

So, we are renaming bundle agent main to bundle agent mpf_main so that you can run some arbitrary policy file which itself somehow includes services/main.cf.

What do I need to do?

Upgrade your policy framework as usual just be sure that you don’t ignore the upstream change to services/main.cf. If you have variables (e.g. main.myvar, default:main.my_var) or methods promises ( usebundle => main, usebundle => default:main you should update them to mpf_main.

This change comes with policy from the upcoming 3.20.0 release, it’s backwards compatible, so it’s perfectly safe to run new policy with this change on binaries prior to 3.20.0.