mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
Fixes Cluster 3a ca-inject YAML parse: when a role variant disables its DB (enabled=false), the depends_on template emitted ` :` because the existing `not shared` guard fired for a non-existing DB host. The new derived flag mirrors `service.required` (= enabled AND shared) and is consumed in: - depends_on/dbms_base.yml.j2, dmbs_excl.yml.j2 (the actual fix) - sys-svc-rdbms postgres/mariadb service templates - web-app-discourse, web-app-snipe-it (latent same-pattern defensives) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
213 lines
6.8 KiB
Python
213 lines
6.8 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from ansible.errors import AnsibleError
|
|
from ansible.plugins.lookup import LookupBase
|
|
|
|
from utils.cache.applications import get_merged_applications
|
|
from utils.roles.applications.config import get
|
|
from utils.roles.applications.services.database import (
|
|
get_database_service_config,
|
|
resolve_database_service_key,
|
|
)
|
|
from utils.roles.entity_name import get_entity_name
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
"""
|
|
Resolve database values for a given database_consumer_id.
|
|
|
|
API (STRICT):
|
|
- {{ lookup('database', database_consumer_id) }}
|
|
- {{ lookup('database', database_consumer_id, 'url_full') }}
|
|
|
|
Notes:
|
|
- want-path is optional and MUST be the second positional argument if used
|
|
- kwarg want= is NOT supported (use positional want-path)
|
|
"""
|
|
|
|
def run(
|
|
self,
|
|
terms: list[Any],
|
|
variables: dict[str, Any] | None = None,
|
|
**kwargs: Any,
|
|
) -> list[Any]:
|
|
terms = terms or []
|
|
if len(terms) not in (1, 2):
|
|
raise AnsibleError("database: requires database_consumer_id [, want_path]")
|
|
|
|
# STRICT: do not support legacy want= kwarg at all
|
|
if "want" in kwargs and str(kwargs.get("want") or "").strip():
|
|
raise AnsibleError(
|
|
"database: kwarg 'want=' is not supported; use positional want_path "
|
|
"like lookup('database', <id>, 'url_full')"
|
|
)
|
|
|
|
consumer_id = str(terms[0]).strip()
|
|
if not consumer_id:
|
|
raise AnsibleError("database: database_consumer_id must not be empty")
|
|
|
|
# STRICT positional want-path (optional)
|
|
want = str(terms[1]).strip() if len(terms) == 2 else ""
|
|
if not want:
|
|
want = "all"
|
|
|
|
vars_ = variables or self._templar.available_variables
|
|
applications = get_merged_applications(
|
|
variables=vars_,
|
|
roles_dir=kwargs.get("roles_dir"),
|
|
templar=getattr(self, "_templar", None),
|
|
)
|
|
path_instances = self._require_var(vars_, "DIR_COMPOSITIONS")
|
|
|
|
consumer_entity = get_entity_name(consumer_id)
|
|
|
|
try:
|
|
dbtype = resolve_database_service_key(applications, consumer_id)
|
|
except ValueError as exc:
|
|
raise AnsibleError(f"database: {exc}") from exc
|
|
|
|
database_service = get_database_service_config(applications, consumer_id)
|
|
enabled = bool(database_service.get("enabled", False))
|
|
shared = bool(database_service.get("shared", False))
|
|
|
|
# If no direct database service is configured: keep behavior similar to the
|
|
# historical empty-value lookup payload.
|
|
if not dbtype:
|
|
resolved = {
|
|
"id": "",
|
|
"enabled": enabled,
|
|
"shared": shared,
|
|
"local": bool(enabled and not shared),
|
|
"type": "",
|
|
"name": consumer_entity,
|
|
"instance": "",
|
|
"host": "",
|
|
"container": "",
|
|
"network": "",
|
|
"username": consumer_entity,
|
|
"password": "",
|
|
"port": "",
|
|
"env": "",
|
|
"initdb_dir": "",
|
|
"build_dir": "",
|
|
"url_jdbc": "",
|
|
"url_full": "",
|
|
"volume": "",
|
|
"image": "",
|
|
"version": "",
|
|
"reach_host": "127.0.0.1",
|
|
}
|
|
return [resolved if want == "all" else resolved.get(want, "")]
|
|
|
|
# Central/shared DB if shared==True
|
|
central_enabled = shared
|
|
db_id = f"svc-db-{dbtype}"
|
|
|
|
central_name = get(
|
|
applications,
|
|
db_id,
|
|
f"services.{dbtype}.name",
|
|
strict=False,
|
|
default="",
|
|
skip_missing_app=True,
|
|
)
|
|
central_name = (str(central_name) if central_name is not None else "").strip()
|
|
|
|
name = consumer_entity
|
|
instance = central_name if central_enabled else name
|
|
host = central_name if central_enabled else "database"
|
|
container = dbtype if central_enabled else f"{consumer_entity}-database"
|
|
network = dbtype if central_enabled else consumer_entity
|
|
username = consumer_entity
|
|
|
|
password = get(
|
|
applications,
|
|
consumer_id,
|
|
"credentials.database_password",
|
|
strict=False,
|
|
default="",
|
|
)
|
|
|
|
port = get(
|
|
applications,
|
|
db_id,
|
|
f"services.{dbtype}.ports.local.{dbtype}",
|
|
strict=False,
|
|
default="",
|
|
skip_missing_app=True,
|
|
)
|
|
|
|
default_version = get(
|
|
applications,
|
|
db_id,
|
|
f"services.{dbtype}.version",
|
|
strict=False,
|
|
default="",
|
|
skip_missing_app=True,
|
|
)
|
|
|
|
version = get(
|
|
applications,
|
|
consumer_id,
|
|
f"services.{dbtype}.version",
|
|
strict=False,
|
|
default=default_version,
|
|
)
|
|
|
|
image = get(
|
|
applications,
|
|
db_id,
|
|
f"services.{dbtype}.image",
|
|
strict=False,
|
|
default=dbtype,
|
|
skip_missing_app=True,
|
|
)
|
|
|
|
# env path without compose dict
|
|
env_dir = f"{path_instances}{get_entity_name(consumer_id)}/.env/"
|
|
env = f"{env_dir}{dbtype}.env"
|
|
initdb_dir = f"{path_instances}{get_entity_name(consumer_id)}/.initdb.d/"
|
|
build_dir = f"{path_instances}{get_entity_name(consumer_id)}/.postgres-build/"
|
|
|
|
jdbc_scheme = dbtype if dbtype == "mariadb" else "postgresql"
|
|
url_jdbc = f"jdbc:{jdbc_scheme}://{host}:{port}/{name}"
|
|
url_full = f"{dbtype}://{username}:{password}@{host}:{port}/{name}"
|
|
|
|
volume_prefix = f"{consumer_entity}_" if not central_enabled else ""
|
|
volume = f"{volume_prefix}{host}"
|
|
|
|
resolved = {
|
|
"id": db_id,
|
|
"enabled": enabled,
|
|
"shared": shared,
|
|
"local": bool(enabled and not shared),
|
|
"type": dbtype,
|
|
"name": name,
|
|
"instance": instance,
|
|
"host": host,
|
|
"container": container,
|
|
"network": network,
|
|
"username": username,
|
|
"password": password,
|
|
"port": port,
|
|
"env": env,
|
|
"initdb_dir": initdb_dir,
|
|
"build_dir": build_dir,
|
|
"url_jdbc": url_jdbc,
|
|
"url_full": url_full,
|
|
"volume": volume,
|
|
"image": image,
|
|
"version": version,
|
|
"reach_host": "127.0.0.1",
|
|
}
|
|
|
|
return [resolved if want == "all" else resolved.get(want, "")]
|
|
|
|
@staticmethod
|
|
def _require_var(vars_: dict[str, Any], key: str) -> Any:
|
|
if key not in vars_:
|
|
raise AnsibleError(f"database: required variable '{key}' is not set")
|
|
return vars_[key]
|