During a lunch break in Munich last week, Michael mentioned Ansible rulebooks, and I realized I had not taken the time to look into them.

Rulebooks are the system by which Ansible is told which events to use in Event-Driven Ansible. They are written in YAML and contain three main components: sources which define the event sources to be used, rules which define conditionals matched from sources, and actions which trigger what should occur when a condition is met.

Here’s a small example I’ve cobbled together to test Event-Driven Ansible (EDA).

- name: Rulebook to do something
  hosts: localhost
  gather_facts: false

  sources:
      - ansible.eda.webhook:
          host: 127.0.0.1
          port: 6000

      - ansible.eda.file_watch:
          path: "files/"
          recursive: false
          ignore_regexes: [ '.*\.o' ]
  rules:
      - name: Launch playbook on start cmd
        condition: event.payload.cmd == 'start'
        action:
           run_playbook:
               name: jp01.yml
               extra_vars:
                   dessert: "{{ event.payload.data }}"
                   home: "{{ HOME }}"       # from environment
                   person: "{{ person }}"   # from vars

      - name: trigger on range
        condition: event.change == 'created' # 'modified'
        action:
           run_module:
               name: copy
               module_args:
                   src: "{{ event.src_path }}"
                   dest: "/tmp/files"

My rulebook defines two sources: the first listens to HTTP webhooks on port 6000, and the second watches (requires pip install watchdog) a directory for new files.

Then I define two rules:

  • the first matches the cmd element in the HTTP payload to the word start and performs an action on match. The action run_playbook launches the specified playbook using the inventory we give ansible-rulebook.
  • the second matches when the event indicates a file has changed in the directory and invokes an Ansible module (copy) to copy new discovered file to a particular destination.

I launch the rulebook:

$ ansible-rulebook -i inventory \
	-r rulebook.yml \
	--vars v.yml \
	-E HOME \
	--print-events

I then POST a webhook and create a new file in the watched directory:

$ curl -H 'Content-type: application/json' \
	-d "$(jo cmd='start' data='chocolate mousse')" \
	http://localhost:6000

$ ls > $dir/files/n01

On the console I can observe the events and what they trigger:

{   'meta': {   'endpoint': '',
                'headers': {   'Accept': '*/*',
                               'Content-Length': '41',
                               'Content-Type': 'application/json',
                               'Host': 'localhost:6000',
                               'User-Agent': 'curl/7.87.0'},
                'received_at': '2023-08-14T11:09:59.195936Z',
                'source': {   'name': 'ansible.eda.webhook',
                              'type': 'ansible.eda.webhook'},
                'uuid': 'c5241a36-3c11-403e-9899-1c43209c858f'},
    'payload': {'cmd': 'start', 'data': 'chocolate mousse'}}

PLAY [localhost] ***************************************************************

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "**** From /Users/jpm. Would you like some chocolate mousse, Jane?"
}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

{   'change': 'created',
    'meta': {   'received_at': '2023-08-14T11:10:17.550916Z',
                'source': {   'name': 'ansible.eda.file_watch',
                              'type': 'ansible.eda.file_watch'},
                'uuid': '6878c7a9-0454-414b-a820-0ec788da599f'},
    'root_path': 'files/',
    'src_path': '/Users/jpm/take/training/rulebook/files/n01',
    'type': 'FileCreatedEvent'}
localhost | CHANGED => {
    "changed": true,
    "checksum": "fe142a4c6a82a55a1c7156fda0056c9f479c5b0c",
    "dest": "/tmp/files/n01",
    "gid": 20,
    "group": "staff",
    "md5sum": "1033373ec84317c9920fcf8e11635e1e",
    "mode": "0644",
    "owner": "jpm",
    "size": 39689,
    "src": "/Users/jpm/.ansible/tmp/ansible-tmp-1692011418.0594718-31843-165094790951764/source",
    "state": "file",
    "uid": 501
}

Internally ansible-rulebook uses the Java Drools rule engine and ansible-runner to actually invoke Ansible content. Rulebooks can gather facts which can be used in conditions. ansible-rulebook appears to ignore ansible.cfg as far as I can tell, and it requires me passing it an inventory explicitly. As shown in my example above, I can pass variables to it (from JSON or YAML files), and have it import environment variables – both useful for introducing API keys and whatnot.

I’ve only just begun toying with Event-Driven Ansible (and have reported an issue with watch_file which may not even be one), but it appears to work quite well so far.

I think this will be most interesting when interfacing with, say, repository pushes etc. which can then trigger Ansible playbook runs.

Further reading: