Recently, I’ve been playing around with the ansible promise type by Fabio Tranchitella. It’s amazing how easy it is to leverage the benefits of agents with all the existing automation from the ansible community. Hence, I wanted to share it in a blog post, with an emphasis on easy.
Let’s start off by creating a new cfbs project in a cfengine-ansible directory.
Make sure to answer yes on the prompt to build on top of the default policy set.
You may answer the remaining prompts as you see fit.
cfbs init
Please enter the name of this CFEngine Build project [Example project]: cfengine-ansible
Please enter the description of this CFEngine Build project [Example description]: Example of running Ansible playbooks with CFEngine
Do you want cfbs to initialize a git repository and make commits to it? [YES/y/no/n] no
Initialized an empty project called 'cfengine-ansible' in 'cfbs.json'
Do you wish to build on top of the default policy set, masterfiles? (Recommended) [YES/y/no/n]: yes
Added module: masterfiles
Since ansible is not a built-in promise type in CFEngine, we’ll need to add it to our project from CFEngine Build.
cfbs add promise-type-ansible
Added module: library-for-promise-types-in-python (Dependency of promise-type-ansible)
Added module: promise-type-ansible
The addition of the custom promise type doesn’t pull in Ansible on its own.
Hence, we’ll need make sure it’s installed on all the hosts.
This can easily be achieved with a simple packages promise.
Create the file ansible/ansible.cf within the cfbs project and add the following bundle.
bundle agent ansible_install
{
packages:
"ansible";
}Next we can create our first playbook in ansible/playbook.yaml that simply touches a file.
- name: My first play
hosts: localhost
tasks:
- name: Making changes
ansible.builtin.file:
path: /tmp/cfengine-ansible-probe
state: touchUnfortunately, or rather thankfully, the playbook won’t run on its own. We’ll have to explicitly tell CFEngine to run it using the ansible promise type (that we added earlier).
bundle agent ansible_playbook
{
ansible:
"$(this.promise_dirname)/playbook.yaml";
}For the sake of convenience, let’s create one more bundle as a single entry point for calling the other bundles.
bundle agent ansible_main
{
methods:
"ansible_install";
"ansible_playbook";
}If we now add our ansible directory to our cfbs project,
we’ll ensure that all of its contents will be copied into the build.
Furthermore, cfbs will automatically detect the policy file and the bundles within it.
When prompted, select ansible_main as the bundle to evaluate.
cfbs add ./ansible/
Which bundle should be evaluated (added to bundle sequence)?
1. ./ansible/ansible.cf:ansible_install (default)
2. ./ansible/ansible.cf:ansible_playbook
3. ./ansible/ansible.cf:ansible_main
4. (None)
[1/2/3/4]: 3
Added module: ./ansible/
Now let’s build and hope for the happy chipmunk.
cfbs build
---snip---
Build complete, ready to deploy 🐿
-> Directory: out/masterfiles
-> Tarball: out/masterfiles.tgz
To install on this machine: sudo cfbs install
To deploy on remote hub(s): cf-remote deploy
After installing / deploying the project according to the hints provided in the build output, you should be able to run the agent and see that the task successfully changed.
sudo cf-agent --no-lock --inform
[WARNING]: No inventory was parsed, only implicit localhost is available
info: Task 'Making changes' successfully changed
Furthermore, we can stat the file to make sure we’ve been talking to our beloved cf-agent and not a hallucinating AI agent.
stat /tmp/cfengine-ansible-probe
File: /tmp/cfengine-ansible-probe
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: 202,1 Inode: 2955 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2026-07-31 14:14:05.346516492 +0000
Modify: 2026-07-31 14:14:05.346516492 +0000
Change: 2026-07-31 14:14:05.346516492 +0000
Birth: 2026-07-31 12:32:03.662445650 +0000
Indeed, everything works as expected. However, there was that annoying warning from Ansible complaining about a missing inventory file. Let’s get rid of that by providing an inventory file using the following bundle to render it.
bundle agent ansible_inventory
{
files:
"$(sys.workdir)/ansible/inventory.ini"
content => "[local]
localhost ansible_connection=local";
}Next, we need to tell the ansible promise type where the inventory file is.
Update the ansible_playbook bundle with the inventory attribute.
bundle agent ansible_playbook
{
ansible:
"$(this.promise_dirname)/playbook.yaml"
inventory => "$(sys.workdir)/ansible/inventory.ini";
}Last, but not least, add the new inventory bundle to our main bundle.
bundle agent ansible_main
{
methods:
"ansible_install";
"ansible_inventory";
"ansible_playbook";
}If we build and deploy again, we can observe that the warning is gone on the next agent run.
sudo cf-agent -KI
info: Task 'Making changes' successfully changed
And that concludes this blog post. However, if you need something more production ready, you should probably go ahead with a similar approach to what Craig Comstock did in his blog post on ansible.
If you have any questions about the ansible promise type, let us know at GitHub Discussions. We are eager to hear from you.