Skip to content

feat(gemini): An Ansible playbook to propagate whimsical ApocalypsAI-themed 'whispers' as messages of the day (MOTD) across managed servers.#5116

Open
polsala wants to merge 1 commit into
mainfrom
ai/gemini-20260705-2054
Open

feat(gemini): An Ansible playbook to propagate whimsical ApocalypsAI-themed 'whispers' as messages of the day (MOTD) across managed servers.#5116
polsala wants to merge 1 commit into
mainfrom
ai/gemini-20260705-2054

Conversation

@polsala

@polsala polsala commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Implementation Summary

  • Utility: nightly-motd-whisperer
  • Provider: gemini
  • Location: ansible-playbooks/nightly-nightly-motd-whisperer
  • Files Created: 6
  • Description: An Ansible playbook to propagate whimsical ApocalypsAI-themed 'whispers' as messages of the day (MOTD) across managed servers.

Rationale

  • Automated proposal from the Gemini generator delivering a fresh community utility.
  • This utility was generated using the gemini AI provider.

Why safe to merge

  • Utility is isolated to ansible-playbooks/nightly-nightly-motd-whisperer.
  • README + tests ship together (see folder contents).
  • No secrets or credentials touched.
  • All changes are additive and self-contained.

Test Plan

  • Follow the instructions in the generated README at ansible-playbooks/nightly-nightly-motd-whisperer/README.md
  • Run tests located in ansible-playbooks/nightly-nightly-motd-whisperer/tests/

Links

  • Generated docs and examples committed alongside this change.

Mock Justification

  • Not applicable; generator did not introduce new mocks.

…themed 'whispers' as messages of the day (MOTD) across managed servers.
@polsala

polsala commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

🤖 Review by GROQ Agent

✅ What's solid

  • Isolation & Scope – All new files live under ansible‑playbooks/nightly‑nightly‑motd‑whisperer, keeping the utility self‑contained and avoiding side‑effects on the rest of the repo.
  • Idempotence – The copy module automatically checks the file checksum, so repeated runs won’t rewrite /etc/motd unless the selected whisper actually changes.
  • Deterministic Randomnessrun_once: true + delegate_to: localhost guarantees the same random message is propagated to every host in a single execution, which is exactly what the feature description promises.
  • Clear Handler – Even though a handler isn’t strictly required for /etc/motd, the debug handler provides useful visibility during ad‑hoc runs and can be extended later (e.g., logging to syslog).
  • Variable‑driven messages – Storing the whispers in vars/motd_messages.yml makes customization trivial for downstream users.

🧪 Tests

  • Test Structure – The test playbook exercises both check mode and a real run, then validates the final file content against the known whitelist. This covers the core functional path.
  • Opportunities for robustness
    • include_tasks with apply: { check_mode: yes } works, but the more idiomatic way to run a playbook in check mode is to invoke ansible-playbook with --check. Consider adding a separate CI job that runs the playbook with --check to avoid any subtle differences in task‑level apply semantics.
    • The test writes to the real /etc/motd on the host executing the CI job. In a shared runner this could affect other processes or violate the principle of test isolation. A safer pattern is to:
      - name: Create a temporary MOTD path
        ansible.builtin.tempfile:
          state: file
          suffix: .motd
        register: tmp_motd
      
      - name: Override destination for the copy task (via extra vars)
        ansible.builtin.set_fact:
          motd_dest: "{{ tmp_motd.path }}"
      
      # In the main playbook, replace hard‑coded /etc/motd with {{ motd_dest | default('/etc/motd') }}
      This keeps the production playbook unchanged while allowing the test to validate logic against a disposable file.
    • The random selection means the test could pass even if the playbook never actually writes a file (e.g., if the copy task were accidentally removed). Adding an explicit changed_when check on the copy task or asserting that run_result.changed is true when not in check mode would tighten the guarantee.
    • Consider adding a negative test that runs the playbook a second time without changing the inventory or variables and asserts run_result.changed == false. This confirms true idempotence.

🔒 Security

  • Privilege Escalation – The playbook uses become: yes to write to /etc/motd. Ensure that the inventory or any host‑specific vars do not contain ansible_become_password or other secrets. If you ever need to store a sudo password, prefer using Ansible Vault rather than plain text.
  • File Permissions – The copy task sets mode 0644, which is appropriate for a world‑readable MOTD. Verify that the target systems’ umask or SELinux policies don’t unintentionally broaden access.
  • Input Validation – Since the messages are static YAML strings, there’s no injection risk. If you ever allow user‑supplied messages (e.g., via an external API), sanitize them before they reach the copy module to avoid accidental command injection via shell‑escaped characters.
  • Inventory Exposure – The default inventory only contains localhost. When users add remote hosts, remind them to keep the inventory file out of version control if it contains hostnames or IPs that could be considered sensitive.

🧩 Docs / Developer Experience

  • README Accuracy – The repository path in the usage section (cd ApocalypsAI/ansible‑playbooks/nightly‑motd‑whisperer) differs from the actual folder name (nightly‑nightly‑motd‑whisperer). Align the instructions to avoid confusion.
  • Running Tests – The README points to ansible‑playbook -i tests/inventory_test.ini tests/test_motd_whisperer.yml. It would be helpful to add a one‑liner for CI, e.g.:
    # Run tests locally
    ansible-playbook -i tests/inventory_test.ini tests/test_motd_whisperer.yml
    # Run in check mode only
    ansible-playbook -i tests/inventory_test.ini tests/test_motd_whisperer.yml --check
  • Customization Guide – While the README mentions editing vars/motd_messages.yml, a short snippet showing how to override the variable from the command line would be nice:
    ansible-playbook -i src/inventory.ini src/motd_whisperer.yml \
      -e "motd_messages=['Custom message 1','Custom message 2']"
  • Idempotence Explanation – Adding a brief note that the copy module will report “changed” only when the selected whisper differs from the current file content helps users understand the playbook’s behavior in CI pipelines.
  • License / Attribution – Since the utility is generated by the Gemini AI provider, consider adding a small attribution line (e.g., “Generated with Gemini AI”) in the README to satisfy any downstream licensing requirements.

🧱 Mocks / Fakes

  • Current State – No explicit mocks are used, which is fine for a simple file‑copy playbook.
  • Future Testability – If the utility evolves (e.g., pulling messages from an external API), you’ll want to mock that API call. Ansible’s uri module can be wrapped with ansible-test or molecule fixtures that provide a local HTTP server returning deterministic JSON.
  • Handler Testing – The debug handler is currently not exercised by the test suite. If you later replace it with a real logging action (e.g., syslog), consider adding an assertion that the handler runs, perhaps by checking the run_result.handlers list.

Overall impression: The playbook delivers the promised functionality in a clean, isolated manner, and the accompanying documentation makes it approachable for both operators and developers. Tightening the test isolation, clarifying a few README details, and adding a couple of idempotence checks will make the utility even more robust and CI‑friendly.

@polsala

polsala commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

🤖 Review by GROQ Agent

✅ What's solid

  • Clear purpose & isolation – The playbook lives in its own nightly‑nightly‑motd‑whisperer directory and does not touch any existing infrastructure.
  • Idempotent design – Using the copy module with a static content ensures the file is only changed when the selected whisper differs from the current /etc/motd.
  • Random selection logicset_fact with | random and run_once: true guarantees a single message is chosen and propagated to every host, which is exactly the behaviour described in the README.
  • Handler for logging – Although not strictly required for /etc/motd, the debug handler provides useful visibility during ad‑hoc runs.
  • README completeness – The documentation explains prerequisites, usage, customization, and testing steps, and includes a nice example output.

🧪 Tests

Observation Recommendation
The test playbook uses include_tasks to pull in ../src/motd_whisperer.yml. This file is a playbook, not a task list, so include_tasks will fail or be ignored. Replace with import_playbook: ../src/motd_whisperer.yml (or run the playbook via ansible-playbook -i … -C for check mode).
apply: check_mode: yes is not a valid argument for include_tasks. Use the --check flag when invoking the test playbook, e.g. ansible-playbook -i tests/inventory_test.ini tests/test_motd_whisperer.yml --check.
The “dry‑run” assertion only checks that check_result is defined. It does not verify that no changes would be made. Add an assertion on check_result.changed (expect false) and optionally inspect check_result.diff for the expected /etc/motd diff.
The real‑run part writes to the actual /etc/motd on the host running the CI. This can be noisy and may interfere with other processes. Run the test against a temporary directory using the dest parameter overridden via a variable, e.g. motd_path: /tmp/motd_test. Then set dest: "{{ motd_path }}" in the playbook when a test flag is true.
No explicit verification that the handler was triggered. Capture run_result and assert run_result.handlers contains "Log MOTD update" (or check run_result.changed).
The test suite lacks a negative case (e.g., ensure the playbook does not modify /etc/motd when the current content already matches a valid whisper). Add a step that runs the playbook a second time and asserts run_result.changed == false.

Suggested minimal test rewrite (excerpt)

- name: Test MOTD Whisperer – real run
  hosts: localhost
  gather_facts: false
  vars:
    motd_path: /tmp/motd_test   # safe location for CI
  tasks:
    - name: Run the playbook (real)
      import_playbook: ../src/motd_whisperer.yml
      vars:
        motd_dest: "{{ motd_path }}"   # override dest via extra var in playbook
      register: run_real

    - name: Verify file was created
      ansible.builtin.stat:
        path: "{{ motd_path }}"
      register: motd_stat
      failed_when: not motd_stat.stat.exists

    - name: Decode content
      ansible.builtin.slurp:
        src: "{{ motd_path }}"
      register: raw
    - set_fact:
        decoded: "{{ raw.content | b64decode | trim }}"

    - name: Assert content is a known whisper
      ansible.builtin.assert:
        that: decoded in motd_messages

(You would need to modify motd_whisperer.yml to accept a motd_dest variable with a default of /etc/motd.)

🔒 Security

  • Privilege escalation – The playbook uses become: yes to write to /etc/motd. Ensure that only trusted operators can trigger this playbook; consider adding a become_user: root explicit declaration for clarity.
  • Message sanitisation – The messages are static, but a user could edit vars/motd_messages.yml and inject shell‑style escape sequences or control characters. While the copy module writes raw text, it’s prudent to document that messages must be plain UTF‑8 strings and avoid characters that could affect terminal rendering (e.g., \x1b escape codes).
  • File permissions – The playbook sets /etc/motd to 0644. This is standard, but if the environment has stricter policies (e.g., SELinux enforcing system_u:object_r:motd_t:s0), you may need to add an seuser/selevel attribute or a restorecon task.
  • Inventory exposure – The default inventory contains only localhost. If users add remote hosts, ensure that the inventory file does not inadvertently expose credentials (e.g., via ansible_ssh_private_key_file). The README could remind contributors to keep inventory files out of version control.

🧩 Docs / Developer Experience

  • Naming consistency – The directory is nightly-nightly-motd-whisperer while the README and usage examples refer to nightly-motd-whisperer. Align the naming to avoid confusion (nightly-motd-whisperer is sufficient).
  • Path references – In the README, the clone command points to cd ApocalypsAI/ansible‑playbooks/nightly‑motd‑whisperer, but the actual path includes the extra nightly- prefix. Update the instructions to match the real location.
  • Variable overrides – Document how a user could change the destination path (useful for testing) by exposing a motd_dest variable with a default of /etc/motd.
  • Idempotence explanation – Mention that the copy module will only rewrite the file when the content differs, which is why the playbook is safe to run repeatedly.
  • Future extensibility – Suggest adding a tags: block (e.g., tags: motd) so users can run --tags motd in larger playbooks.

🧱 Mocks / Fakes

  • The PR notes “Not applicable; generator did not introduce new mocks.” While true for production code, the test suite does rely on a form of mocking (running in check_mode). To make the tests more deterministic and CI‑friendly:
    • Use the ansible-test framework or Molecule with a Docker/Podman driver to spin up an isolated container where /etc/motd can be safely written.
    • Provide a small fake inventory fixture (already present) but also a fake vars fixture that deliberately contains a known sentinel value (e.g., "TEST_SENTINEL"). The test can then assert that the sentinel appears in the target file, eliminating randomness.
    • If you keep the random selection, seed the Jinja2 random filter via ANSIBLE_RANDOM_SEED environment variable in the CI step to make the outcome reproducible for debugging.

Overall, the utility is well‑structured and documented, but tightening the test implementation, clarifying security considerations, and polishing the documentation will make the contribution more robust and easier for downstream users to adopt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant