mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
Two findings of the CodeRabbit review of #869. jmap_call.yml counted notCreated and notUpdated as a refused call, but not notDestroyed. A destroy that Stalwart rejected therefore passed, and the post-load flush cleared stalwart_app_password_stale while the retired app password stayed valid. A notDestroyed entry now fails the call, except notFound, where the password is already gone. The password keeps its retired description, so the next deploy collects it again and retries the destroy. tracing.yml took any Stdout tracer with an include policy for the ban tracer, so one that includes other events kept the role from creating its own and bans stayed off the container output. The selector now also requires every event of STALWART_BAN_TRACER. Verified with the real task files, a stubbed container command and canned JMAP responses: a forbidden destroy now ends with failed=1, while notFound and a clean destroy pass; with an unrelated include tracer the role makes a second JMAP call that creates the ban tracer, with a complete one it does not. The previous files pass the forbidden destroy and skip the creation in both tracer cases. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
49 lines
2.4 KiB
YAML
49 lines
2.4 KiB
YAML
---
|
||
# Reusable management JMAP call: in `stalwart_jmap_body` (JSON), out `stalwart_jmap`.
|
||
# Param: stalwart_jmap_tolerate_rejected - true when the caller reads a set-level rejection itself (e.g. alreadyExists).
|
||
# Param: stalwart_jmap_act_as - account address the recovery admin acts for; unset runs the call as the admin itself.
|
||
- name: "🔧 Stalwart JMAP call"
|
||
ansible.builtin.shell:
|
||
cmd: >-
|
||
container exec -i {{ STALWART_CONTAINER_ADDRESS }}
|
||
{{ 30 | curl }} -sS -f -K -
|
||
-H "Content-Type:application/json"
|
||
{{ STALWART_JMAP_URL }}
|
||
stdin: |-
|
||
user = {{ ((stalwart_jmap_act_as ~ '%' if stalwart_jmap_act_as is defined else '') ~ STALWART_ADMIN_USER ~ ':' ~ STALWART_ADMIN_PASSWORD) | to_json(ensure_ascii=False) }}
|
||
data-binary = {{ stalwart_jmap_body | to_json(ensure_ascii=False) }}
|
||
register: stalwart_jmap_raw
|
||
changed_when: false
|
||
retries: 12
|
||
delay: 5
|
||
until: >-
|
||
stalwart_jmap_raw.rc == 0
|
||
and (
|
||
(stalwart_jmap_raw.stdout | from_json).methodResponses[0][0] != 'error'
|
||
or (stalwart_jmap_raw.stdout | from_json).methodResponses[0][1].type
|
||
not in STALWART_JMAP_TRANSIENT_ERRORS
|
||
)
|
||
timeout: "{{ lookup('timeout', 120) | int }}"
|
||
# nocheck: include-uniform-apply-keyword — seven call sites include this file; the in-file no_log is the SPOT, per-site apply would repeat it 7×
|
||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||
|
||
- name: "🔧 Parse Stalwart JMAP response"
|
||
ansible.builtin.set_fact:
|
||
stalwart_jmap: "{{ stalwart_jmap_raw.stdout | from_json }}"
|
||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||
|
||
- name: "✅ Ensure Stalwart accepted the JMAP call"
|
||
ansible.builtin.assert:
|
||
that:
|
||
- stalwart_jmap.methodResponses[0][0] != 'error'
|
||
- stalwart_jmap_rejected | length == 0
|
||
fail_msg: "Stalwart refused the JMAP call {{ stalwart_jmap.methodResponses[0][0] }}: {{ stalwart_jmap_rejected or stalwart_jmap.methodResponses[0][1] }}"
|
||
quiet: true
|
||
vars:
|
||
stalwart_jmap_rejected: >-
|
||
{{ {} if (stalwart_jmap_tolerate_rejected | default(false) | bool)
|
||
else (stalwart_jmap.methodResponses[0][1].notCreated | default({}, true))
|
||
| combine(stalwart_jmap.methodResponses[0][1].notUpdated | default({}, true))
|
||
| combine(stalwart_jmap.methodResponses[0][1].notDestroyed | default({}, true)
|
||
| dict2items | rejectattr('value.type', 'equalto', 'notFound') | items2dict) }}
|