Writing a cfbs module for your custom policy update

Posted by Nick Anderson
February 14, 2022

I re-stumbled across this mailing list post from Bryan Burke about some policy framework upgrade issues where he also asked about hooking in and customizing the update policy. I thought this sounded like a good opportunity for an example using a cfbs module. So, let’s take a look at making a cfbs module for a custom update policy.

As mentioned in the thread there are just a couple of things you need to do in order to hook in and customize the behavior of the update policy.

To define your own update mechanism:

  1. Add your custom update policy to update inputs
  2. Set the name of the bundle to use for policy update

In creating the cfbs module I first created a repository to host it and added a README and LICENSE. Then following my own cfbs module layout style I made policy/custom-policy-update.cf, cfbs/def.json and cfbs.json. For this simple example we won’t expose any tunables for the module, maybe we will do that in a future post.

policy/custom-policy-update.cf introduces the my_organization namespace so as to not conflict with other pre-existing bundle names.

body file control
{
  namespace => "my_organization";
}

bundle agent custom_policy_update, a bundle to run some things before and after the default policy update. Note: the use of action => defaut:u_immediate. This body is part of the update policy, it’s a copy of body action immediate from the standard library which makes sure that a promise is not skipped during this execution of cf-agent because of a held lock (remember, CFEngine locks each promise for 1 minute by default). The update policy tries to be completely stand alone not loading any of the same policy files as another policy entry to try and decrease chances of breaking policy update from some other change.

bundle agent custom_policy_update
{
  vars:
    # The bundle that the MPF runs by default:
    # https://github.com/cfengine/masterfiles/search?q=%22bundle+agent+cfe_internal_update_policy_cpv%22
    "mpf_default_policy_update_bundle"
      string => "default:cfe_internal_update_policy_cpv";

  methods:

    # We want to do some things before the stock policy update runs
    "pre_default_policy_update"
      action => default:u_immediate;

    # We want to use the stock policy update (or, maybe we don't ...)
    "$(mpf_default_policy_update_bundle)"
      action => default:u_immediate;

    # We want to do some things before the stock policy update runs
    "post_default_policy_update"
      action => default:u_immediate;

}

bundle agent pre_default_policy_update, a bundle to run before the default policy update bundle which simply emits a report that it is running for illustrative purposes.

bundle agent pre_default_policy_update
{
  reports:
    default:verbose_mode|my_orginization:DEBUG_custom_policy_update::
      "Running $(this.namespace):$(this.bundle) ..."
        action => default:u_immediate;
}

And finally, bundle agent post_default_policy_update a bundle to run after the default policy update, perhaps fetching additional information to prepare for the full policy run. Of course we don’t have to use the default bundle at all, we could implement our own bundle to update inputs instead.

cfbs/def.json contains the data that we want to merge into def.json in the root of the policy set during cfbs build to include and run the policy.

{
  "vars": {
    "update_inputs": ["services/custom-update-policy/custom-update-policy.cf"],
    "mpf_update_policy_bundle": "my_organization:custom_policy_update"
  }
}

cfbs.json contains the metadata necessary to allow the module to be added directly from the repository (since this isn’t in the index at build.cfengine.com).

{
  "name": "custom-policy-update",
  "description": "Customize the CFEngine Masterfiles Policy Framework Update Policy.",
  "type": "module-repo",
  "provides": {
    "custom-policy-update": {
      "description": "Customize the CFEngine Masterfiles Policy Framework Update Policy.",
      "tags": ["example"],
      "repo": "https://github.com/nickanderson/https://github.com/nickanderson/example-cfengine-mpf-custom-policy-update",
      "by": "https://github.com/nickanderson/",
      "steps": [
        "copy policy/custom-policy-update.cf services/custom-update-policy/custom-update-policy.cf",
        "json cfbs/def.json def.json"
      ]
    }
  }
}

Now, with everything in place we bring up a test host and try it out.

First, we initialize a cfbs project.

[root@hub ~]# mkdir -p /tmp/cfbs; cd /tmp/cfbs
[root@hub cfbs]# cfbs init
Initialized - edit name and description cfbs.json
To add your first module, type: cfbs add masterfiles

Next we add masterfiles from the default index and custom-policy-update from its repository directly.

[root@hub cfbs]# cfbs add masterfiles
Added module: masterfiles
[root@hub cfbs]# cfbs add https://github.com/nickanderson/example-cfengine-mpf-custom-policy-update custom-policy-update
Added module: custom-policy-update

Finally we build and install …

[root@hub cfbs]# cfbs build

Modules:
001 masterfiles          @ f3a8f65e77428a6ab9d62c34057a7ace6ae54ce9 (Downloaded)
002 custom-policy-update @ d6470d23835c0dd2d39c3c1c600c89f2b73fa4eb (Downloaded)

Steps:
001 masterfiles          : run './prepare.sh -y'
001 masterfiles          : copy './' 'masterfiles/'
002 custom-policy-update : copy 'policy/custom-policy-update.cf' 'masterfiles/services/custom-update-policy/custom-update-policy.cf'
002 custom-policy-update : json 'cfbs/def.json' 'masterfiles/def.json'

Generating tarball...

Build complete, ready to deploy 🐿
 -> Directory: out/masterfiles
 -> Tarball:   out/masterfiles.tgz

To install on this machine: cfbs install
To deploy on remote hub(s): cf-remote deploy --hub hub out/masterfiles.tgz
[root@hub cfbs]# cfbs install
Installed to /var/cfengine/masterfiles

Now, we can see that it works running the update policy. With --define my_orginization:DEBUG_custom_policy_update the pre and post bundles emit reports about the fact that they are running.

[root@hub cfbs]# cf-agent -KIf /var/cfengine/masterfiles/update.cf  --define my_orginization:DEBUG_custom_policy_update
R: Running my_organization:pre_default_policy_update ...
    info: Copied file '/var/cfengine//masterfiles/services/custom-update-policy/custom-update-policy.cf' to '/var/cfengine/inputs/services/custom-update-policy/custom-update-policy.cf.cfnew' (mode '600')
    info: Backed up '/var/cfengine/inputs/services/custom-update-policy/custom-update-policy.cf' as '/var/cfengine/inputs/services/custom-update-policy/custom-update-policy.cf.cfsaved'
    info: Moved '/var/cfengine/inputs/services/custom-update-policy/custom-update-policy.cf.cfnew' to '/var/cfengine/inputs/services/custom-update-policy/custom-update-policy.cf'
    info: Updated '/var/cfengine/inputs/services/custom-update-policy/custom-update-policy.cf' from source '/var/cfengine//masterfiles/services/custom-update-policy/custom-update-policy.cf' on 'localhost'
    info: Purged '/var/cfengine/inputs/services/custom-update-policy/custom-update-policy.cf.cfsaved' copy dest directory
    info: Object '/var/cfengine//masterfiles/./services/custom-update-policy/custom-update-policy.cf' had permissions 0644, changed it to 0600
    info: Object '/var/cfengine//masterfiles/./services/custom-update-policy' had permissions 0755, changed it to 0700
    info: Object '/var/cfengine//masterfiles/./def.json' had permissions 0644, changed it to 0600
    info: Object '/var/cfengine//masterfiles/.' had permissions 0755, changed it to 0700
R: Running my_organization:post_default_policy_update ...
R: Found user specified update bundle.
R: User specified update bundle: my_organization:custom_policy_update
[root@hub cfbs]#

That’s all folks!