Bookmark this page

Creating and Testing Ansible Rulebooks

Objectives

  • Read, write, and test basic Ansible Rulebooks that react to events from various sources.

Reading and Writing Ansible Rulebooks

Ansible Rulebooks contain rulesets, which are comprised of hosts, sources, and rules. The following example is a simple rulebook that contains one ruleset with a single event source and a single rule:

---
- name: Listen for events on a webhook 1
  hosts: localhost 2

  sources: 3
    - ansible.eda.webhook: 4
        host: 0.0.0.0
        port: 5000

  rules: 5
    - name: Update prod servers 6
      condition: event.payload.message == "force-update" 7
      action: 8
        run_playbook: 9
          name: playbook.yml

1

The name of the ruleset. Each ruleset must have a unique name in the rulebook. This is similar to the name of a play in an Ansible Playbook.

2

In an Ansible Rulebook, the hosts directive defines the hosts on which the actions run when a rule matches. This is similar to the hosts directive used by plays in a playbook.

3

The sources section lists the event sources that provide events relevant to this ruleset.

4

The event source plug-in to use, followed by directives that configure it. Although not necessary, you might add the name key to describe the event source. In this example, the ruleset uses the ansible.eda.webhook event source plug-in and listens on all available IPv4 addresses on TCP port 5000.

5

The rules section lists one or more rules to determine if an action or actions should be taken when an event is received.

6

The name of the rule. Each rule in a ruleset must have a name that is unique in the ruleset. This is similar to the name of a task in a play. (A better comparison might be to the name of a handler in a play.)

7

The condition section determines if an action should be taken based on the data in an event. In this example the condition matches if the event.payload.message key from the event has the string force-update as its value. String values must be encapsulated in single quotes or double quotes.

8

The action directive specifies an action to carry out if the condition is met. You can instead use the actions directive to specify a list of several actions to perform if the condition is met.

9

The action to take. In this example, the run_playbook action is used to run the playbook.yml playbook file in the current working directory of the ansible-rulebook command. You can specify an absolute path or relative path to a playbook for the name directive. You can alternatively specify the fully qualified collection name (FQCN) for a playbook in an Ansible Content Collection.

Selecting Actions for Rules

Rules support a number of actions. The following table shows some examples of supported actions.

ActionDescription
run_playbook Runs an Ansible Playbook on the local file system
run_job_template Runs a job template on an automation controller
run_workflow_template Runs a workflow job template on an automation controller
run_module Runs a single module (similar to an Ansible ad hoc command)
post_event Sends an event to the running ruleset in the rule engine
print_event Writes the event to stdout
debug Writes debugging information to stdout

Examples of how to use some of these actions follow later in this section. A complete list of actions with additional documentation is available at https://ansible.readthedocs.io/projects/rulebook/en/stable/actions.html.

Actions on an Automation Controller

You need to configure the rule engine with the URL and authentication credentials for your automation controller if you use the run_job_template or run_workflow_template actions. For example, you might change the preceding Ansible Rulebook to run a job template named Update packages on prod servers in the Production organization on your automation controller:

---
- name: Listen for events on a webhook
  hosts: host.lab.example.com

  sources:
    - ansible.eda.webhook:
        host: 0.0.0.0
        port: 5000

  rules:
    - name: Update prod servers
      condition: event.payload.message == "force-update"
      action:
        run_job_template: 1
          name: Update packages on prod servers 2
          organization: Production 3

1

The action is to run a job template on the automation controller.

2

The name of the job template.

3

The name of the organization on the automation controller to which the job template belongs.

The URL and authentication token for the automation controller must be configured in the rule engine. If you are using the ansible-rulebook command as your rule engine, it has environment variables or the --controller-url and --controller-token command-line options for this purpose. How to use the ansible-rulebook command to test rulebooks is covered in more detail at the end of this section.

Event Source Plug-ins and Sample Rulebooks

If you install the ansible.eda Ansible Content Collection, then you can access its files in the collections/ansible_collections/ansible/eda directory.

The event source plug-ins provided by this Ansible Content Collection are in the extensions/eda/plugins/event_source directory. The plug-ins are Python text files that might contain supplementary documentation on how to use these event sources.

In addition, the extensions/eda/rulebooks subdirectory of the Ansible Content Collection contains sample Ansible Rulebooks. You can use them as examples to help create your own rulebooks.

[user@host eda]$ ls -1 extensions/eda/rulebooks/
demo_controller_rulebook.yml
git-hook-deploy-rules.yml
git-hook-test-rules.yml
github-ci-cd-rules.yml
hello_events.yml
journald_events.yml
kafka-test-rules.yml
local-test-rules.yml
process_down.yml

In the following subsections, examples of Ansible Rulebooks that use three event sources are examined in more detail:

  • ansible.eda.webhook, which provides a webhook to which you can send events

  • ansible.eda.journald, which processes events sent to the local journald log service

  • ansible.eda.url_check, which polls content at a particular URL and monitors its status

Reacting to Webhook Events

You can use the ansible.eda.webhook event source to create a webhook to which a remote service can send events that Event-Driven Ansible then acts upon.

The extensions/eda/rulebooks/git-hook-deploy-rules.yml rulebook provides an example that runs a playbook and prints to stdout when an incoming request to the webhook contains specific content:

---
- name: Local Deploy Git Hook Rules
  hosts: all
  sources:
    - ansible.eda.webhook:
        port: 5001
  rules:
    - name: run tests 1 1
      condition: event.payload.repo == "{{repo}}" 2
      action:
        run_playbook: 3
          name: ansible.eda.continuous_integration 4
          var_root: payload 5
          post_events: true 6
    - name: print output 7
      condition: event.output is defined
      action:
        print_event: 8
          var_root: output
...

This sample rulebook creates a socket on TCP port 5001 to listen to incoming web requests, and contains two rules.

1

The first rule is called run tests 1.

2

The rule has a condition that is met if the value of the event.payload.repo key matches the value of the repo variable.

Note

Variables cannot be defined in rulebooks.

They must be provided by using the --vars or --env-vars command line arguments.

3

If the condition is met, then the action is to run the run_playbook action.

4

The run_playbook action is configured to run the continuous_integration playbook from the ansible.eda Ansible Content Collection.

5

The var_root variable is set to the value of payload. This setting enables the playbook to use shorter variable names. For example, the playbook can use the shorter event.repo variable rather than the longer event.payload.repo variable.

6

The post_events Boolean is set to true to allow the artifacts from the playbook execution to be inserted back into the ruleset as events.

7

The second rule called print output has a condition that states if the event.output key is defined, meaning there was output from the previous event, then run the print_event action.

8

If the condition is met, then the action is to run the print_event action. The print_event action in this example prints the output of the event to stdout.

Important

When you create a webhook in an Ansible Rulebook, it opens the TCP port but does not adjust the system firewall. You must ensure that firewalld is configured to permit traffic to that network service to pass through the firewall.

In addition, TCP ports 1 through 1023 are privileged ports. If a rulebook is run by a non-root user and the port specified in the rulebook is in the privileged port range, then the rulebook fails to run.

Only one port can bind to an IP address at a time. If a rulebook is running and listening on a specific port, then a second rulebook must listen on a different port or a different IP address.

The following table lists the mandatory arguments for the ansible.eda.webhook event source plug-in.

ArgumentDescription
host The hostname to listen to; defaults to 0.0.0.0 (all interfaces)
port The TCP port to listen to; defaults to 5000

Additional arguments are available to configure a TLS certificate or client authentication for the webhook.

ArgumentDescription
token An optional authentication token expected from client
certfile The optional path to a certificate file to enable TLS support
keyfile The optional path to a key file to be used together with certfile
password The optional password to be used when loading the certificate chain
hmac_secret The optional HMAC secret used to verify the payload from the client
hmac_algo The optional HMAC algorithm used to calculate the payload hash. Defaults to sha256
hmac_header The optional HMAC header sent by the client with the payload signature, the default is x-hub-signature-256
hmac_format The optional HMAC signature format; supported formats are hex (the default) and base64.

The following example rulebook uses the ansible.eda.webhook event source plug-in:

---
- name: Listen for events on a webhook
  hosts: all

  sources:
    - name: Match events posted to port 5000
      ansible.eda.webhook:
        host: 192.168.5.10
        port: 5000

  rules:
    - name: Run actions if the webhook has a payload
      condition: event.payload is defined
      actions: 1
        - run_module:
            name: ansible.builtin.debug
            module_args:
              msg: "Webhook received"
        - run_playbook:
            name: log_write.yml
            extra_vars:
              message: "Message 001"

1

When you want to use multiple actions for the same rule, the actions keyword replaces action and each action is a list item.

Reacting to Log Events

Event-Driven Ansible can use the ansible.eda.journald event source plug-in to react to log events recorded by the local system journal.

The extensions/eda/rulebooks/journald_events.yml rulebook provides an example of printing messages related to sudo events:

---
- name: Journald events
  hosts: all
  sources:
    - name: Match all messages
      ansible.eda.journald:
        match: "ALL"
  rules:
    - name: Print sudo journald event message
      condition: event.journald._comm == 'sudo'
      action:
        print_event:
          pretty: true
          var_root:
            journald.message: journald.message
...

This rulebook tests all journal messages and then has a rule that displays the message for any event that matches the sudo command.

Rather than processing all journal events, you might only process specific journal events. The top of the extensions/eda/plugins/event_source/journald.py file displays examples of other matching criteria:

...output omitted...
Examples:
--------
    - name: Return severity 6 messages
      ansible.eda.journald:
        match: "PRIORITY=6"

    - name: Return messages when sudo is used
      ansible.eda.journald:
        match: "_EXE=/usr/bin/sudo"

...output omitted...

Note

The systemd.journal-fields(7) man page contains information about the journal fields that you can match on.

The journalctl command with the -o verbose option displays log entries with all fields. You might use this information to help construct rulebook conditions.

For example, the following command displays all journal fields for log entries related to the SSHD service:

[user@host ~]$ sudo journalctl _SYSTEMD_UNIT=sshd.service -o verbose
Mon 2024-01-22 13:41:33.974742 UTC [s=e9ee3399119d415eafd57bbc7b04fc83;i=219;b=da5b9bb0c9cc461da9d89ae42be98731;m=c7c33f;t=60f88f>
    PRIORITY=6
    _BOOT_ID=da5b9bb0c9cc461da9d89ae42be98731
    _MACHINE_ID=8317e4e5bd33466ebe75abdc86a14055
    _HOSTNAME=workstation
    _RUNTIME_SCOPE=system
    _UID=0
    _GID=0
    _SYSTEMD_SLICE=system.slice
    _CAP_EFFECTIVE=1ffffffffff
    _TRANSPORT=syslog
    SYSLOG_FACILITY=10
    SYSLOG_TIMESTAMP=Jan 22 13:41:33
    SYSLOG_IDENTIFIER=sshd
    SYSLOG_PID=1355
    MESSAGE=Server listening on 0.0.0.0 port 22.
...output omitted...

Important

When used in a condition for a rule, the ansible.eda.journald event source plug-in converts the journal key names to lowercase. For example, if you want to check the value of the SYSLOG_IDENTIFIER journal key, then your condition must check the value of the event.journald.syslog_identifier event key.

The following sample rulebook prints journal events that match the workstation host where the sshd service was either reloaded or restarted:

---
- name: Journald events
  hosts: all
  sources:
    - name: Match SSHD messages
      ansible.eda.journald:
        match: "_SYSTEMD_UNIT=sshd.service" 1
  rules:
    - name: Print SSHD SIGHUP and signal 15 events
      condition: > 2
        ((event.journald.message == 'Received SIGHUP; restarting.') or
        (event.journald.message == 'Received signal 15; terminating.') and
        (event.journald._hostname == 'workstation'))
      action:
        print_event:
          pretty: true

1

This example is one way of matching sshd related events. There are other ways to match events related to sshd. For example, you can match journal events by searching for events that contain the following fields and values:

  • _COMM=sshd

  • _SYSLOG_IDENTIFIER=sshd

  • _EXE=/usr/sbin/sshd

2

Use the greater-than sign to split a condition over multiple lines. You can use the and keyword to specify that multiple conditions must match. You can use the or keyword to specify a match on any condition. You can also group conditions by using parentheses. In this example, one of two specific messages must match and the hostname must be workstation.

Reacting to URL Check Events

Event-Driven Ansible can react to the status of a URL by using the ansible.eda.url_check event source plug-in.

The extensions/eda/rulebooks/ directory does not contain an example rulebook for this event source, but the extensions/eda/plugins/event_source/url_check.py plug-in file contains code similar to the following example near the top of the file:

...output omitted...
    - name: check web server
      ansible.eda.url_check:
        urls:
          - http://192.0.2.56:8000/docs
        delay: 10
...output omitted...

The previous example polls http://192.0.2.56:8000/docs every 10 seconds and sends the status of the URL as an event. The event returns the status, status_code, and url keys with their corresponding values.

The following rulebook uses the ansible.eda.url_check event source plug-in:

---
- name: Listen for URL events
  hosts: all

  sources:
    - name: Match events from web application 1
      ansible.eda.url_check:
        urls:
          - http://webapp.example.lab.com
        delay: 60

  rules:
    - name: Web application is up 2
      condition: event.url_check.status == "up"
      action:
        run_module:
          name: ansible.builtin.debug
          module_args:
            msg: "Web application is up"

    - name: Web application error 3
      condition: event.url_check.status_code == 500
      action:
        run_playbook:
          name: redeploy_webapp.yml

    - name: Web application is down 4
      condition: event.url_check.status == "down"
      action:
        run_playbook:
          name: restart_services.yml

1

In this example the ansible.eda.url_check event source plug-in polls the web application every 60 seconds.

2

In the first rule, if the event.url_check.status key has the up value, then the rule runs the ansible.builtin.debug module to print the message "Web application is up".

3

In the second rule, if the event.url_check.status_code key has the 500 value, then the rule runs the redeploy_webapp.yml playbook to redeploy the web application.

4

In the third rule, if the event.url_check.status key has the down value, then the rule runs the restart_services.yml playbook to restart the web services.

Testing Ansible Rulebooks

You can use the ansible-rulebook command to test rulebooks. The following example demonstrates the basic syntax for using the ansible-rulebook command:

[user@host ~]$ ansible-rulebook -i example_inventory -r example_rulebook.yml

The following table shows some arguments available to the ansible-rulebook command.

ArgumentDescription
-r RULEBOOK or --rulebook RULEBOOK Specifies the rulebook to run.
-e VARS or --vars VARS Specifies a variable file to pass to the rulebook.
-E ENV_VARS or --env-vars ENV_VARS A comma-separated list of variables to import from your environment.
-v or --verbose Print more debug messages. Use -vv to print even more verbosely.
-S SOURCE_DIR Add a source directory. You might use this if you intend to use a custom event source plug-in.
-i INVENTORY or --inventory INVENTORY Specify the inventory file or directory to use.
--print-events Print events to stdout.

When you run a rulebook with the ansible-rulebook command, the process runs until you press Ctrl+C in the terminal or until it is otherwise interrupted.

The following example runs the webhook_example.yml rulebook and uses the hosts in the specified inventory file:

[user@host ~]$ ansible-rulebook -i inventory -r webhook_example.yml

Unless there are errors in the rulebook, running the preceding command produces no output until a rule's condition matches and triggers an action. The following example demonstrates output that you might see when a rule matches and its action causes ansible-rulebook to run a playbook:

[user@host ~]$ ansible-rulebook -i inventory -r webhook_example.yml
PLAY [Take action from a webhook event] ****************************************

TASK [Gathering Facts] *********************************************************
ok: [servera]
ok: [serverb]

TASK [A task in a playbook] ****************************************************
changed: [servera]
changed: [serverb]

PLAY RECAP *********************************************************************
servera    : ok=2    changed=1    unreachable=0    failed=0 ...
serverb    : ok=2    changed=1    unreachable=0    failed=0 ...

The --print-events argument can be used with the ansible-rulebook command to print the result of any events in your rulebook to stdout. This can be especially useful if you are unsure what the resulting data is or what the formatting looks like in the events from your rulebook.

The following example shows the output when the ansible-rulebook command is run using the --print-events argument, and a webhook event causes a rule to run an action:

[user@host ~]$ ansible-rulebook -i inventory -r webhook_example.yml --print-events
{   'meta': {   'endpoint': '',
                'headers': {   'Accept': '/',
                               'Content-Length': '56',
                               'Content-Type': 'application/json',
                               'Host': 'rulebook-host:5000',
                               'User-Agent': 'curl/7.76.1'},
                'received_at': '2024-02-01T22:07:23.720101Z',
                'source': {   'name': 'Match events posted to port 5000',
                              'type': 'ansible.eda.webhook'},
                'uuid': 'e641ca23-b585-4ad1-9a6c-7f21527be5b7'},
    'payload': {'this_is_a_key_from_json': 'this is a value from json'}}

PLAY [wrapper] *****************************************************************
...output_omitted...

In the previous example the event was printed before the action was run. This shows that the event.payload key contains the this_is_a_key_from_json key with the this is a value from json value.

Revision: do274-2.4-65daa25