This tutorial will show you how to add custom inventory attributes that can be leveraged in policy and reported on in the CFEngine Enterprise Mission Portal. For a more detailed overview on how the inventory system works please reference CFEngine 3 inventory modules.


This tutorial provides instructions for the following:

Note: This tutorial uses the CFEngine Enterprise Vagrant Environment and files located in the vagrant project directory are automatically available to all hosts.

Choose an attribute to inventory

Writing inventory policy is incredibly easy. Simply add the inventory and attribute_name= tags to any variable or namespace scoped classes.

In this tutorial we will add Owner information into the inventory. In this example we will use a simple shared flat file data source /vagrant/inventory_owner.csv.

On your hosts create /vagrant/inventory_owner.csv with the following content:

hub, Operations Team <>
host001, Development <>

Create and deploy inventory policy

Now that each of your hosts has access to a data source that provides the Owner information we will write an inventory policy to report that information.

Create /var/cfengine/masterfiles/services/tutorials/inventory/ with the following content:
bundle agent tutorials_inventory_owner
# @brief Inventory Owner information
# @description Inventory owner information from `/vagrant/inventory_owner.csv`.
    "data_source" string => "/vagrant/inventory_owner.csv";
      data => data_readstringarray( $(data_source), "", ", ", 100, 512 ),
      if => fileexists( $(data_source) );

      string  => "$(owners[$(sys.uqhost)][0])",
      meta    => { "inventory", "attribute_name=Owner" },
      comment => "We need to tag the owner information so that it is correctly
                  reported via the UI.";

      "$(this.bundle): Discovered Owner='$(my_owner)'"
        if => isvaribale( "my_owner" );
bundle agent __main__
# @brief Run tutorials_inventory_owner if this policy file is the entry
  methods: "tutorials_inventory_owner";

Note: For the simplicity of this tutorial we assume that masterfiles is not configured for policy updates from a Git repository. If it is, please add the policy to your repository and ensure it gets to its final destination as needed.

This policy will not be activated until it has been included in inputs. For simplicity we will be adding it via Augments (def.json).

Create /var/cfengine/masterfiles/def.json and populate it with the following content:

  "inputs": [ "services/tutorials/inventory/" ],
  "vars": {
    "control_common_bundlesequence_end": [ "tutorials_inventory_owner" ]

Any time you modify something, it is always a good idea to validate the syntax. You can run cf-promises to check policy syntax.

Policy Validation:

[root@hub ~]# cf-promises -cf /var/cfengine/masterfiles/
[root@hub ~]# echo $?

No output and return code 0 indicate the policy was successfully validated.

JSON Validation:

You can use your favorite JSON validate. I like jq, plus it's handy for picking apart API responses so let's install that and use it.

[root@hub ~]# wget -q -O /var/cfengine/bin/jq
[root@hub ~]# chmod +x /var/cfengine/bin/jq

Once it's installed, we can use it to validate our JSON.

[root@hub ~]# jq '.' < /var/cfengine/masterfiles/def.json
  "inputs": [
  "vars": {
    "control_common_bundlesequence_end": [
[root@hub ~]# echo $?

Pretty printed JSON and a return code of 0 indicate the JSON was successfully validated.

You can also perform a manual policy run and check that the correct owner is discovered.

Manual Policy Run:

cf-agent -KIf /var/cfengine/masterfiles/ -b tutorials_inventory_owner
    info: Using command line specified bundlesequence
R: tutorials_inventory_owner: Discovered Owner='Operations Team <>'

Here we ran the policy without locks (-K) in inform mode (-I), using a specific policy entry (-f) and activating only a specific bundle (-b). The inform output helps us confirm that the owner is discovered from our CSV properly.


Once you have integrated the policy into def.json it will run by all agents after they have updated their policy. Once the hub has had a chance to collect reports the Owner attribute will be available to select as a Table column for Inventory reports. Custom attributes appear under the User defined section.

Note: It may take up to 15 minutes for your custom inventory attributes to be collected and made available for reporting.

Mission Portal

custom inventory attribute

You will see the host owner as shown in the following report.

custom inventory report

Inventory API

Of course, you can also get this information from the Inventory API.

Let's query the API from the hub itself, and use jq to make it easier to handle the output.

Now that we have jq in place, let's query the Inventory API to see what inventory attributes are available.

curl -s -k --user admin:admin -X GET https://localhost/api/inventory/attributes-dictionary | jq '.[].attribute_name'
"BIOS vendor"
"BIOS version"
"CFEngine Enterprise license file"
"CFEngine Enterprise license status"
"CFEngine Enterprise license utilization"
"CFEngine Enterprise licenses allocated"
"CFEngine ID"
"CFEngine roles"
"CFEngine version"
"CPU logical cores"
"CPU model"
"CPU physical cores"
"CPU sockets"
"Disk free (%)"
"Host name"
"IPv4 addresses"
"MAC addresses"
"Memory size (MB)"
"OS kernel"
"OS type"
"Physical memory (MB)"
"Policy Release Id"
"Policy Servers"
"Ports listening"
"Primary Policy Server"
"System UUID"
"System manufacturer"
"System product name"
"System serial number"
"System version"
"Timezone GMT Offset"
"Uptime minutes"

Yes, we can see our attribute Owner is reported.

Now, let's query the Inventory API to see what Owners are reported.

curl -s -k --user admin:admin -X POST -H 'content-type: application/json' -d '{ "select": [ "Host name", "Owner" ]}' https://localhost/api/inventory | jq '.data[].rows[]'
  "Development <>"
  "Operations Team <>"

Indeed, we can see each host reporting the values as expected from our CSV file.