mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
The repo's async convention is fire and forget: ASYNC_POLL is 0 whenever ASYNC_ENABLED is true, and the swarm test lane forces it true through extra vars. A task dispatched that way returns a stub, so a changed_when or failed_when reading its own register evaluates against nothing and can never report changed and never fail. Several tasks carried a "not ASYNC_ENABLED and" prefix in front of those conditions, added to avoid touching undefined rc and stdout, which turned the guard off entirely rather than adapting it. That is how the nextcloud plugin configuration stayed broken unseen: the batch reported ok after 0.8s while its delegated commands kept running, the next task read the half written configuration, and no failure in any of them could surface. Every async in the repo was decided individually rather than swept. Where nothing consumes the result, the async stays and a one line rationale now records what does not read it. Where a later task, a handler or a browser does consume it, either the async goes or the result is collected. Collecting is what the new reaper does. It waits for a whole batch, then reports every broken job at once instead of aborting on the first, so one run shows whether a single item or the whole set is broken. Deciding what counts as broken lives in a filter plugin, because the distinction is subtle enough to deserve tests: a module reports failure in msg, a command in rc and stdout, an idempotent API answers "already exists" and means success, and a job that cannot be read at all comes back marked finished with no outcome, which would otherwise read as green. Timing cost is roughly one to two minutes on a deploy measured in hours. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
54 lines
3.0 KiB
YAML
54 lines
3.0 KiB
YAML
---
|
|
- name: "🔍 Resolve {{ application_id }} manager hosting node + container id"
|
|
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/resolve_host_cid.yml') }}"
|
|
vars:
|
|
resolve_service_key: "{{ TAIGA_MANAGER_SERVICE }}"
|
|
resolve_output_prefix: TAIGA_MANAGER
|
|
resolve_compose_cid: "{{ TAIGA_MANAGER_CONTAINER }}"
|
|
|
|
- name: "⏳ Wait for taiga-back to apply migrations (manager must not re-apply)"
|
|
ansible.builtin.shell: > # nocheck: container-exec-resolver runtime-resolved CID, delegated to the hosting node
|
|
container exec -i
|
|
{{ TAIGA_MANAGER_LOCAL_CID | quote }}
|
|
python manage.py migrate --check
|
|
register: taiga_migrate_check
|
|
until: taiga_migrate_check.rc == 0
|
|
retries: 30
|
|
delay: 10
|
|
changed_when: false
|
|
delegate_to: "{{ TAIGA_MANAGER_HOST_NODE }}"
|
|
|
|
- name: "📁 Create Taiga admin user (idempotent)"
|
|
ansible.builtin.shell: > # nocheck: container-exec-resolver runtime-resolved CID, delegated to the hosting node
|
|
container exec -i
|
|
-e DJANGO_SUPERUSER_PASSWORD={{ TAIGA_SUPERUSER_PASSWORD | quote }}
|
|
{{ TAIGA_MANAGER_LOCAL_CID | quote }}
|
|
python manage.py createsuperuser --noinput
|
|
--username {{ TAIGA_SUPERUSER_NAME }}
|
|
--email {{ TAIGA_SUPERUSER_EMAIL }}
|
|
register: taiga_create_admin
|
|
delegate_to: "{{ TAIGA_MANAGER_HOST_NODE }}"
|
|
changed_when: taiga_create_admin.rc == 0
|
|
failed_when: >
|
|
taiga_create_admin.rc != 0 and
|
|
('already taken' not in (taiga_create_admin.stdout + taiga_create_admin.stderr) | lower) and
|
|
('already exists' not in (taiga_create_admin.stdout + taiga_create_admin.stderr) | lower) and
|
|
('integrityerror' not in (taiga_create_admin.stdout + taiga_create_admin.stderr) | lower)
|
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
|
|
- name: "📝 Upsert Taiga admin via manage.py shell"
|
|
ansible.builtin.shell: > # nocheck: container-exec-resolver runtime-resolved CID, delegated to the hosting node
|
|
container exec -i
|
|
-e DJANGO_SUPERUSER_PASSWORD={{ TAIGA_SUPERUSER_PASSWORD | quote }}
|
|
{{ TAIGA_MANAGER_LOCAL_CID | quote }}
|
|
python manage.py shell -c
|
|
"from django.contrib.auth import get_user_model; import os; U=get_user_model(); u,created=U.objects.get_or_create(username='{{ TAIGA_SUPERUSER_NAME }}'); changed=bool(created);
|
|
old=(u.email,u.is_staff,u.is_superuser,u.is_active); u.email='{{ TAIGA_SUPERUSER_EMAIL }}'; u.is_staff=True; u.is_superuser=True; u.is_active=True; changed =
|
|
changed or old!=(u.email,u.is_staff,u.is_superuser,u.is_active); pwd=os.environ.get('DJANGO_SUPERUSER_PASSWORD'); assert pwd, 'Missing DJANGO_SUPERUSER_PASSWORD';
|
|
need_pwd = not u.check_password(pwd); changed = changed or need_pwd; need_pwd and u.set_password(pwd); u.save(); print('CHANGED=1' if changed else 'CHANGED=0')"
|
|
register: taiga_upsert_admin
|
|
when: taiga_create_admin.rc != 0
|
|
delegate_to: "{{ TAIGA_MANAGER_HOST_NODE }}"
|
|
changed_when: "'CHANGED=1' in (taiga_upsert_admin.stdout + taiga_upsert_admin.stderr)"
|
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|