Files
computer-playbook/plugins/lookup/compose_only.py
kevinveenbirkenbachandClaude Opus 4.8 fbe50d10ed fix(svc-runner): make the runner role swarm-compatible
Workers failed 'docker build: no Dockerfile': the build context, compose file, .env and runner binary were staged on the stack host only while every node deploys its own node-local runner stack. Build and push the image once on the manager (RUNNER_IMAGE_REF via the swarm registry, 04_image.yml) and let workers pull it; render compose.yml (template with lstrip_blocks, matching the replaced stack_host_template action), .env and the extracted run.sh on every node. Add a node_local kwarg to the compose_networks/container_networks/compose_restart/compose_only lookups so this node-local compose stack renders a bridge default network (workers cannot create overlays), keeps restart: unless-stopped (ephemeral runners died after one job) and keeps container_name under swarm; default is unchanged for every other role. Treat build/install.stamp as stale when the venv is missing and exclude ./build from the nested-src copy, closing the nested-e2e ruamel failure class. Regression tests for the node_local render paths; the network include tests now accept lookup kwargs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 02:37:26 +02:00

64 lines
2.3 KiB
Python

"""Lookup `compose_only`: emit a `key: "<value>"` YAML pair only in
compose mode; emit nothing under swarm.
A single SPOT for every service-level YAML key that is structurally
incompatible with `docker stack deploy` and must therefore be omitted
when DEPLOYMENT_MODE == 'swarm'. Currently used for:
* `container_name` — swarm rejects it alongside `deploy.replicas > 1`
("can't set container_name and X as container name must be unique").
* `pull_policy` — swarm rejects it as "Additional property pull_policy
is not allowed" (compose-only extension).
Both call sites look identical:
{{ lookup('compose_only', 'container_name', MY_CONTAINER) }}
{{ lookup('compose_only', 'pull_policy', 'never') }}
The value is always double-quoted in the output. That makes the emit
safe for YAML edge cases like `restart: no` (bare `no` would otherwise
parse as a boolean) without burdening the caller with quoting rules.
"""
from __future__ import annotations
import contextlib
from typing import Any
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
class LookupModule(LookupBase):
def run(
self,
terms: list[Any] | None,
variables: dict[str, Any] | None = None,
**kwargs: Any,
) -> list[str]:
if not terms or len(terms) != 2:
raise AnsibleError(
"compose_only lookup requires exactly two terms: the key and the value"
)
key = str(terms[0]).strip()
value = str(terms[1])
if not key:
raise AnsibleError("compose_only lookup: key term must be non-empty")
vars_ = variables or getattr(self._templar, "available_variables", {}) or {}
raw_mode = vars_.get("DEPLOYMENT_MODE", "compose")
templar = getattr(self, "_templar", None)
if templar is not None:
with contextlib.suppress(Exception):
raw_mode = templar.template(raw_mode)
mode_force = vars_.get("compose_mode_force", "")
if templar is not None:
with contextlib.suppress(Exception):
mode_force = templar.template(mode_force)
effective_mode = str(mode_force or raw_mode).strip()
if effective_mode == "swarm" and not bool(kwargs.get("node_local", False)):
return [""]
return [f'{key}: "{value}"']