At Red Hat Tech Day Netherlands 2026 in Bunnik, Fred van Zwieten and Ismail Dhaoui demoed the new Automation Portal in AAP 2.7 — a self-service template gallery where every template has a one-click "Start" button. One of the branch-office templates shown live was Branch - After Hours Access Report, which generates a report of access events that occurred outside normal operating hours. Below is a runnable playbook that implements that use case.

Playbook

---
- name: Branch - After Hours Access Report
  hosts: branch_access_controllers
  gather_facts: false
  vars:
    business_hours_start: "07:00"
    business_hours_end: "19:00"
    report_dir: "/tmp/access-reports"
    report_file: "{{ report_dir }}/{{ inventory_hostname }}-after-hours-{{ ansible_date_time.date | default(lookup('pipe', 'date +%F')) }}.json"

  tasks:
    - name: Ensure local report directory exists
      ansible.builtin.file:
        path: "{{ report_dir }}"
        state: directory
        mode: "0750"
      delegate_to: localhost
      run_once: true

    - name: Pull access log entries from the branch controller
      ansible.builtin.command:
        cmd: >-
          badge-access-cli export
          --since "{{ ansible_date_time.date | default(lookup('pipe', 'date +%F')) }}T00:00:00"
          --format json
      register: access_log_raw
      changed_when: false

    - name: Parse access log JSON
      ansible.builtin.set_fact:
        access_events: "{{ access_log_raw.stdout | from_json }}"

    - name: Filter events outside business hours
      ansible.builtin.set_fact:
        after_hours_events: >-
          {{ access_events | selectattr('event_time_local', 'lt', business_hours_start)
             | list + access_events | selectattr('event_time_local', 'gt', business_hours_end) | list }}

    - name: Write after-hours access report
      ansible.builtin.copy:
        content: "{{ after_hours_events | to_nice_json }}"
        dest: "{{ report_file }}"
        mode: "0640"
      delegate_to: localhost

    - name: Report summary
      ansible.builtin.debug:
        msg: >-
          {{ after_hours_events | length }} after-hours access event(s)
          found for {{ inventory_hostname }}, written to {{ report_file }}.

What it does

The play targets a branch_access_controllers group — one host per branch badge/door controller, or a jump host that can query them. It exports the day's access log as JSON via a badge-access-cli call (a stand-in for whatever access-control vendor CLI or API a given branch runs), then uses selectattr to keep only events whose local timestamp falls before business_hours_start or after business_hours_end. The filtered list is written to a per-host JSON report and a one-line summary is printed for the console or job log.

This is exactly the kind of job the Automation Portal's self-service gallery is built for: a branch manager or security analyst clicks Start on "Branch - After Hours Access Report," the underlying job template runs against the correct inventory, and the operator never has to know which repo the playbook lives in, which credentials it uses, or how the time-window logic works. The portal handles the surveys and credential injection; the playbook stays a normal, testable piece of Ansible content in source control.

Notes / Gotchas

  • Time zones matter. event_time_local assumes the controller already reports local branch time. If your devices log in UTC, add a step to convert with ansible.builtin.set_fact and a Jinja strftime filter (or normalize upstream) before filtering.
  • badge-access-cli is illustrative. Swap in your actual access-control integration — many vendors ship a REST API instead, in which case replace the command task with ansible.builtin.uri.
  • Business hours as survey inputs. In a job template, expose business_hours_start/business_hours_end as survey variables so the same playbook covers branches with different opening hours without editing YAML.
  • Delegate the write, not the read. The report file is written on localhost (the control node / execution environment) so a single consolidated report directory is easy to ship to object storage or a SIEM afterward.
  • Idempotency: this playbook is reporting-only — it never mutates state on the target, so changed_when: false on the log pull keeps job output honest.

Takeaway

A short, focused playbook like this is all the "Branch - After Hours Access Report" template needs behind its one-click Start button — the value AAP 2.7's Automation Portal adds is making that logic discoverable and runnable by non-Ansible-experts, not making the playbook itself more complex.

References