mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-10 12:47:04 +00:00
Nextcloud 34 flags "Task Processing worker status: never run" because only occ taskprocessing:worker records core/ai.taskprocessing_worker_last_iteration, and the role ran background jobs through the cron container alone; since 32.0.7 the worker command is taskprocessing:worker, so background-job:worker would never clear the check. Add a taskprocessing service that reuses the cron image, mounts, healthcheck, restart policy and networks and runs occ taskprocessing:worker -t 60 in an in-container restart loop that backs off while Nextcloud is not yet installed, so swarm neither replaces the task every minute nor marks the stack fatal on a fresh install. It scales with the swarm topology, since task claims use FOR UPDATE SKIP LOCKED. Rendered compose and swarm stacks only gain the taskprocessing service and validate against the compose schema; the new unit tests fail when the worker mount is dropped or background-job:worker is used instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
78 lines
2.8 KiB
Python
78 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
import unittest
|
|
|
|
from jinja2 import ChainableUndefined, Environment, select_autoescape
|
|
|
|
from plugins.lookup.container_volumes import container_volumes
|
|
from utils.cache.files import PROJECT_ROOT, read_text
|
|
from utils.cache.yaml import load_yaml_any
|
|
from utils.roles.mapping import (
|
|
ROLE_FILE_META_SERVICES,
|
|
ROLE_FILE_TEMPL_COMPOSE,
|
|
ROLE_FILE_VARS_MAIN,
|
|
)
|
|
|
|
ROLE = "roles/web-app-nextcloud"
|
|
APPLICATION_ID = "web-app-nextcloud"
|
|
SERVICES = load_yaml_any(str(PROJECT_ROOT / ROLE / ROLE_FILE_META_SERVICES))
|
|
VARS = load_yaml_any(str(PROJECT_ROOT / ROLE / ROLE_FILE_VARS_MAIN))
|
|
TEMPLATE = read_text(str(PROJECT_ROOT / ROLE / ROLE_FILE_TEMPL_COMPOSE))
|
|
|
|
|
|
def _render_jinja(expression: str) -> str:
|
|
env = Environment(
|
|
undefined=ChainableUndefined,
|
|
autoescape=select_autoescape(default_for_string=False),
|
|
)
|
|
env.globals["lookup"] = lambda name, *args, **kwargs: f"<{name} {args!r}>"
|
|
env.filters["path_join"] = lambda parts: "/".join(str(part) for part in parts)
|
|
return env.from_string(expression).render()
|
|
|
|
|
|
def _mounts(service: str) -> str:
|
|
return container_volumes(
|
|
{APPLICATION_ID: {"services": SERVICES}},
|
|
APPLICATION_ID,
|
|
service,
|
|
render_jinja=_render_jinja,
|
|
)
|
|
|
|
|
|
def _service_block(service_var: str) -> str:
|
|
start = TEMPLATE.index(f"{{% set service_name = {service_var} %}}")
|
|
following = re.search(r"\{% (set service_name|if|endif)\b", TEMPLATE[start + 1 :])
|
|
end = start + 1 + following.start() if following else len(TEMPLATE)
|
|
return TEMPLATE[start:end]
|
|
|
|
|
|
class TestTaskProcessingWorker(unittest.TestCase):
|
|
def test_service_key_matches_the_vars_spot(self):
|
|
self.assertIn(VARS["NEXTCLOUD_TASKPROCESSING_SERVICE"], SERVICES)
|
|
|
|
def test_worker_mounts_exactly_what_cron_mounts(self):
|
|
cron = _mounts(VARS["NEXTCLOUD_CRON_SERVICE"])
|
|
self.assertIn("/var/www/html/", cron)
|
|
self.assertEqual(_mounts(VARS["NEXTCLOUD_TASKPROCESSING_SERVICE"]), cron)
|
|
|
|
def test_worker_scales_with_the_swarm_topology(self):
|
|
self.assertNotIn("replicas", SERVICES[VARS["NEXTCLOUD_TASKPROCESSING_SERVICE"]])
|
|
|
|
def test_worker_runs_the_command_that_records_its_heartbeat(self):
|
|
block = _service_block("NEXTCLOUD_TASKPROCESSING_SERVICE")
|
|
self.assertIn("taskprocessing:worker -t 60", block)
|
|
self.assertNotIn("background-job:worker", block)
|
|
|
|
def test_worker_runs_the_cron_image_as_the_occ_owner(self):
|
|
worker = _service_block("NEXTCLOUD_TASKPROCESSING_SERVICE")
|
|
cron = _service_block("NEXTCLOUD_CRON_SERVICE")
|
|
image = "{{ lookup('container_image', application_id, 'nextcloud') }}"
|
|
self.assertIn(image, cron)
|
|
self.assertIn(image, worker)
|
|
self.assertIn('"su", "{{ NEXTCLOUD_DOCKER_USER }}"', worker)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|