Files
computer-playbook/plugins/lookup/scrape_target.py
T
kevinveenbirkenbachandClaude Opus 5 a05071fe78 refactor(roles): let a role declare its entity name and drop the get_ prefix
The entity name keys a role's instance directory, its compose project and its container name, and was derived only by stripping the longest matching category prefix. Two roles under different categories whose names share a suffix therefore collapse onto one entity and overwrite each other's rendered stack, which no provides: can repair.

A literal entity_name in vars/main.yml now wins over the derivation. web-app-seaweedfs-console takes its shorter name back and declares the entity seaweedfs-console, which frees the seaweedfs key in its own services map for the engine consumer and removes the provides:, covers: and alias entries that stood in for it. A new lint pairs the declaration with the primary key it has to match, because a drift between them would drop the role from the provider set in silence.

Compared across all 288 roles, exactly one entity changes, and it restores what main already keeps on disk, so no instance directory moves.

The function and the filter lose their get_ prefix. Two lints read that prefix as the discriminator between the variable and the filter that fills it; both discriminate on the pipe now. Ansible's templar resolves a filter and a same-named variable independently, verified against ansible-core 2.20.2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-03 19:40:53 +02:00

107 lines
3.8 KiB
Python

"""Lookup ``scrape_target``: mode-aware ``<host>:<port>`` for a Prometheus
scrape target. Swarm resolves ``tasks.<entity>_<service_key>`` (per-task DNS);
compose uses ``compose_host`` or the service container name. ``port`` overrides
the ``services.<service_key>.ports.internal.<port_kind>`` lookup."""
from __future__ import annotations
import contextlib
from typing import Any
from ansible.errors import AnsibleError
from ansible.plugins.loader import lookup_loader
from ansible.plugins.lookup import LookupBase
from utils.roles.applications.config import get
from utils.roles.entity.name import entity_name
def _as_str(value: Any) -> str:
return "" if value is None else str(value).strip()
class LookupModule(LookupBase):
def run(
self,
terms: list[Any] | None,
variables: dict[str, Any] | None = None,
**kwargs: Any,
) -> list[str]:
if not terms:
raise AnsibleError("scrape_target lookup requires the application_id term")
application_id = _as_str(terms[0])
if not application_id:
raise AnsibleError("scrape_target: application_id must be non-empty")
service_key = _as_str(terms[1]) if len(terms) > 1 else ""
if not service_key:
raise AnsibleError("scrape_target: service_key must be non-empty")
explicit_port_kind = (
_as_str(terms[2]) if len(terms) > 2 else _as_str(kwargs.get("port_kind"))
)
port_kind = explicit_port_kind or "http"
compose_host = (
_as_str(terms[3]) if len(terms) > 3 else _as_str(kwargs.get("compose_host"))
)
port_override = _as_str(kwargs.get("port"))
if port_override and explicit_port_kind:
raise AnsibleError(
f"scrape_target: {application_id!r} passed both port={port_override!r} "
f"and port_kind={explicit_port_kind!r}; port wins and port_kind would be "
"silently dropped, so pass only one"
)
vars_ = variables or getattr(self._templar, "available_variables", {}) or {}
templar = getattr(self, "_templar", None)
raw_mode = vars_.get("DEPLOYMENT_MODE", "compose")
if templar is not None:
with contextlib.suppress(Exception):
raw_mode = templar.template(raw_mode)
deployment_mode = str(raw_mode).strip()
applications = lookup_loader.get(
"applications", loader=self._loader, templar=getattr(self, "_templar", None)
).run([], variables=vars_)[0]
port = port_override or _as_str(
get(
applications=applications,
application_id=application_id,
config_path=f"services.{service_key}.ports.internal.{port_kind}",
strict=False,
default="",
)
)
if not port:
raise AnsibleError(
f"scrape_target: no internal port for {application_id!r} "
f"(services.{service_key}.ports.internal.{port_kind})"
)
if deployment_mode == "swarm":
entity = entity_name(application_id)
if not entity:
raise AnsibleError(
f"scrape_target: cannot derive entity from {application_id!r}"
)
return [f"tasks.{entity}_{service_key}:{port}"]
host = compose_host or _as_str(
get(
applications=applications,
application_id=application_id,
config_path=f"services.{service_key}.name",
strict=False,
default="",
)
)
if not host:
raise AnsibleError(
f"scrape_target: no compose host for {application_id!r} "
f"(services.{service_key}.name)"
)
return [f"{host}:{port}"]