mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
feat(lint): enforce auth-isolation in variants.yml
Auth-isolated matrix variants (exactly one of LDAP / OIDC-OAuth2 enabled, and no non-auth `enabled: true` alongside) must explicitly pin every non-auth service-key in the role's variant union to literal `enabled: false, shared: false`. Without this, the variant's auth-only test scope balloons into a full integration deploy via the services.yml-default propagation, wasting CI minutes and obscuring the regression signal. Adds `tests/integration/roles/meta/variants/test_auth_isolation.py` (rule key `variants-auth-isolation`) plus the matching catalog entry. The trigger is narrow on purpose: a variant that enables auth alongside other services is a deliberate integration test, not an auth-isolated one, and the rule does not fire there. Mass-fixes the 24 legacy auth-isolated variants the rule turned up: each gets the missing `enabled: false, shared: false` declarations appended to its `services:` block. Where the false-override clashes with a static-literal base in meta/services.yml, the override line also picks up a `# nocheck: variants-static-override` marker. The svc-bkp-container-2-local variant 2 additionally carries `# nocheck: variants-services-match` on the new keys because they are resolver-only matrix hooks with no consumer contract in the base meta. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
515b735522
commit
f63118f34d
27 files changed
+671
No files matched your search
@@ -66,6 +66,7 @@ The placement rule is per check. The catalog "Position" column uses these labels
|
||||
| `native-metrics` | comment block above | [test_native_metrics_contract.py](../../../../tests/integration/roles/meta/services/prometheus/test_native_metrics_contract.py) | Marks a role whose `services.prometheus.native_metrics.enabled` and `templates/prometheus.yml.j2` presence are intentionally inconsistent. Reserved for prepared-but-default-off scrape fragments (operator opt-in). |
|
||||
| `variants-static-override` | same line | [test_variants_no_static_override.py](../../../../tests/integration/roles/meta/services/test_variants_no_static_override.py) | Allows a `roles/*/meta/variants.yml` leaf override of an attribute whose base value in `meta/services.yml` is a static literal. Place on the override line in `variants.yml`. Use only when the consumer chain is provably immune to the variant-merge propagation gap; the default is to make the base value dynamic via `"{{ '<role>' in group_names }}"`. |
|
||||
| `variants-services-match` | same or above | [test_services_match.py](../../../../tests/integration/roles/meta/variants/test_services_match.py) | Allows a `roles/*/meta/variants.yml` `services.<key>` entry to reference a service key that is NOT declared as a top-level entry in `meta/services.yml`. Reserved for resolver-only matrix hooks (typically on invokable infrastructure roles) whose variant entries pin the round's companion topology via service-key flags the resolver maps to provider roles, with no real consumer contract in the base meta. |
|
||||
| `variants-auth-isolation` | same or above | [test_auth_isolation.py](../../../../tests/integration/roles/meta/variants/test_auth_isolation.py) | Exempts an auth-isolated variant entry in `roles/*/meta/variants.yml` (one that enables exactly one of LDAP / OIDC / OAuth2) from the rule that every non-auth service-key in the role's variant union must be pinned to literal `false`. Place on the leading `- services:` line of the variant or on the line above. Use only when the broader closure is intentional. |
|
||||
| `lookup-config-path` | same or above | [config/](../../../../tests/integration/ansible/lookups/config/) | Skips a single `lookup('config', …)` call from path-validation passes. Use when the call resolves against runtime-only state, typically self-referential roles. |
|
||||
| `unused-var` | same or above | [test_role_and_group_vars_used.py](../../../../tests/lint/ansible/variables/test_role_and_group_vars_used.py) | Exempts a top-level key in `roles/<role>/{vars,defaults}/main.yml` or `group_vars/**/*.yml` from the "must be referenced" check. Use when the consumer is non-YAML/Jinja (Python plugins, external tools). |
|
||||
| `project-root-import` | same or above | [test_project_root_import.py](../../../../tests/lint/filesystem/python/test_project_root_import.py) | Permits a local `PROJECT_ROOT` / `parents[N]` / `os.pardir` chain. Reserved for `__main__.py` `sys.path` bootstrap and standalone scripts under `roles/<role>/files/`. The marker MUST carry an inline explanation. |
|
||||
|
||||
@@ -42,3 +42,15 @@
|
||||
ldap: # nocheck: variants-services-match
|
||||
enabled: true
|
||||
shared: true
|
||||
gitea: # nocheck: variants-services-match
|
||||
enabled: false
|
||||
shared: false
|
||||
keycloak: # nocheck: variants-services-match
|
||||
enabled: false
|
||||
shared: false
|
||||
mariadb: # nocheck: variants-services-match
|
||||
enabled: false
|
||||
shared: false
|
||||
postgres: # nocheck: variants-services-match
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -69,3 +69,21 @@
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
mariadb:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -68,3 +68,21 @@
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
postgres:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -69,3 +69,18 @@
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -78,3 +78,21 @@
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
postgres:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -65,3 +65,24 @@
|
||||
oidc:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
mariadb:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -74,3 +74,21 @@
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
ollama:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -74,3 +74,24 @@
|
||||
oauth2:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
mariadb:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -65,3 +65,24 @@
|
||||
oauth2:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
postgres:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -77,3 +77,21 @@
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
mariadb:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -60,3 +60,15 @@
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -61,3 +61,21 @@
|
||||
oauth2:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -66,3 +66,18 @@
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
ollama:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -40,6 +40,27 @@
|
||||
oidc:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
mariadb:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
- services:
|
||||
css:
|
||||
enabled: false
|
||||
|
||||
@@ -87,3 +87,36 @@
|
||||
oidc:
|
||||
enabled: false
|
||||
shared: false
|
||||
collabora:
|
||||
enabled: false
|
||||
shared: false
|
||||
coturn:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
mariadb:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
onlyoffice:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
talk:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -65,3 +65,24 @@
|
||||
oidc:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
postgres:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -69,3 +69,21 @@
|
||||
oidc:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -65,3 +65,24 @@
|
||||
oauth2:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
postgres:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -71,3 +71,27 @@
|
||||
oidc:
|
||||
enabled: false
|
||||
shared: false
|
||||
coturn:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
postgres:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -67,3 +67,24 @@
|
||||
oidc:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
ollama:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -71,3 +71,21 @@
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
mariadb:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -65,3 +65,24 @@
|
||||
oauth2:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
mariadb:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -65,3 +65,24 @@
|
||||
oauth2:
|
||||
enabled: false
|
||||
shared: false
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
mariadb:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -68,3 +68,21 @@
|
||||
css:
|
||||
enabled: false
|
||||
shared: false
|
||||
dashboard:
|
||||
enabled: false
|
||||
shared: false
|
||||
email:
|
||||
enabled: false
|
||||
shared: false
|
||||
logout:
|
||||
enabled: false
|
||||
shared: false
|
||||
matomo:
|
||||
enabled: false
|
||||
shared: false
|
||||
postgres:
|
||||
enabled: false # nocheck: variants-static-override
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -41,3 +41,6 @@
|
||||
oidc:
|
||||
enabled: false
|
||||
shared: false
|
||||
prometheus:
|
||||
enabled: false
|
||||
shared: false
|
||||
@@ -0,0 +1,205 @@
|
||||
"""Integration guard: an **auth-isolated** variant in
|
||||
``roles/<role>/meta/variants.yml`` MUST explicitly disable every
|
||||
non-auth service-key that any other variant of the same role declares.
|
||||
|
||||
Why
|
||||
---
|
||||
|
||||
When a variant pins exactly one auth flavor (LDAP vs. OIDC/OAuth2)
|
||||
the round under test exists to exercise that one auth path. Leaving
|
||||
unrelated services (matomo, prometheus, dashboard, …) unset means
|
||||
their effective value is decided by the deep-merge against
|
||||
``meta/services.yml`` at deploy time — usually a dynamic
|
||||
``"{{ '<role>' in group_names }}"`` expression — which makes the
|
||||
resolver pull every matching provider role into the round's include
|
||||
closure. The auth-only test scope balloons into a full integration
|
||||
deploy, wasting CI minutes and obscuring the regression signal.
|
||||
|
||||
Pinning every non-auth flag to ``enabled: false, shared: false`` in
|
||||
the auth-isolated variant keeps the closure tight: the resolver gets
|
||||
a deterministic, minimal include set and the variant clearly states
|
||||
"only this auth path, nothing else." The cost is a few explicit
|
||||
``false`` lines per variant; the benefit is faster, more focused
|
||||
matrix rounds.
|
||||
|
||||
Trigger
|
||||
-------
|
||||
|
||||
A variant V is **auth-isolated** when:
|
||||
|
||||
1. EXACTLY ONE of the two auth flavors is set to ``enabled: true`` in
|
||||
V's ``services:`` block:
|
||||
|
||||
* LDAP flavor: ``services.ldap.enabled: true``
|
||||
* OIDC/OAuth2 flavor: ``services.oidc.enabled: true`` OR
|
||||
``services.oauth2.enabled: true``
|
||||
|
||||
2. AND no non-auth service-key in V's ``services:`` block has
|
||||
``enabled: true``.
|
||||
|
||||
Variants that enable both auth flavors (a full integration test), or
|
||||
neither (a no-auth baseline / teardown variant), or that pin auth
|
||||
alongside other ``enabled: true`` services (a mixed integration test)
|
||||
are NOT auth-isolated and this rule does not fire for them.
|
||||
|
||||
Obligation
|
||||
----------
|
||||
|
||||
When V is auth-isolated, every service-key K that appears in any
|
||||
variant of this role's ``meta/variants.yml`` AND is not in
|
||||
``{ldap, oidc, oauth2}`` MUST be declared in V as
|
||||
``enabled: false`` AND ``shared: false`` (literal).
|
||||
|
||||
Exemption
|
||||
---------
|
||||
|
||||
Place ``# nocheck: variants-auth-isolation`` on the same line as the
|
||||
variant's leading ``- services:`` (or on the line immediately above)
|
||||
to skip the check for that single variant entry.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from utils.annotations.suppress import is_suppressed_at
|
||||
from utils.cache.files import read_text
|
||||
from utils.cache.yaml import load_yaml_any
|
||||
from utils.roles.mapping import ROLE_FILE_META_VARIANTS
|
||||
|
||||
from . import PROJECT_ROOT
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
ROLES_DIR = PROJECT_ROOT / "roles"
|
||||
|
||||
_RULE = "variants-auth-isolation"
|
||||
_AUTH = frozenset({"ldap", "oidc", "oauth2"})
|
||||
_OIDC_GROUP = frozenset({"oidc", "oauth2"})
|
||||
_LDAP_GROUP = frozenset({"ldap"})
|
||||
|
||||
|
||||
def _is_enabled_true(entry: object) -> bool:
|
||||
return isinstance(entry, dict) and entry.get("enabled") is True
|
||||
|
||||
|
||||
def _auth_isolated(services: dict) -> bool:
|
||||
"""A variant is auth-isolated when exactly one auth flavor is
|
||||
enabled=true in its services map AND no non-auth service-key is
|
||||
enabled=true. A variant that enables auth alongside non-auth
|
||||
services is a mixed integration test and falls outside this rule.
|
||||
"""
|
||||
oidc_on = any(_is_enabled_true(services.get(k)) for k in _OIDC_GROUP)
|
||||
ldap_on = any(_is_enabled_true(services.get(k)) for k in _LDAP_GROUP)
|
||||
if oidc_on == ldap_on:
|
||||
return False
|
||||
for key, entry in services.items():
|
||||
if key in _AUTH:
|
||||
continue
|
||||
if _is_enabled_true(entry):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _variant_header_line_numbers(variants_file: Path) -> dict[int, int]:
|
||||
"""Map variant index (0-based) to the 1-based line number of its
|
||||
leading ``- services:`` (or ``- {}`` / ``- key:`` header).
|
||||
"""
|
||||
lines = read_text(str(variants_file)).splitlines()
|
||||
out: dict[int, int] = {}
|
||||
variant_index = -1
|
||||
for idx, raw in enumerate(lines):
|
||||
if raw.startswith("- "):
|
||||
variant_index += 1
|
||||
out[variant_index] = idx + 1
|
||||
return out
|
||||
|
||||
|
||||
def _is_literal_false(value: object) -> bool:
|
||||
return value is False
|
||||
|
||||
|
||||
def _entry_pinned_false(entry: object) -> bool:
|
||||
return (
|
||||
isinstance(entry, dict)
|
||||
and _is_literal_false(entry.get("enabled"))
|
||||
and _is_literal_false(entry.get("shared"))
|
||||
)
|
||||
|
||||
|
||||
class TestVariantsAuthIsolation(unittest.TestCase):
|
||||
def test_auth_isolated_variants_explicitly_disable_non_auth_services(self):
|
||||
offenders: list[str] = []
|
||||
|
||||
for role_dir in sorted(p for p in ROLES_DIR.iterdir() if p.is_dir()):
|
||||
role_name = role_dir.name
|
||||
variants_file = role_dir / ROLE_FILE_META_VARIANTS
|
||||
if not variants_file.is_file():
|
||||
continue
|
||||
|
||||
try:
|
||||
variants_raw = load_yaml_any(str(variants_file), default_if_missing=[])
|
||||
except Exception as exc:
|
||||
offenders.append(f"{role_name}: meta/variants.yml parse error: {exc}")
|
||||
continue
|
||||
if not isinstance(variants_raw, list):
|
||||
continue
|
||||
|
||||
non_auth_union: set[str] = set()
|
||||
for variant in variants_raw:
|
||||
if not isinstance(variant, dict):
|
||||
continue
|
||||
services = variant.get("services") or {}
|
||||
if not isinstance(services, dict):
|
||||
continue
|
||||
for key in services:
|
||||
if isinstance(key, str) and key not in _AUTH:
|
||||
non_auth_union.add(key)
|
||||
|
||||
header_lines = _variant_header_line_numbers(variants_file)
|
||||
text_lines = read_text(str(variants_file)).splitlines()
|
||||
|
||||
for index, variant in enumerate(variants_raw):
|
||||
if not isinstance(variant, dict):
|
||||
continue
|
||||
services = variant.get("services") or {}
|
||||
if not isinstance(services, dict):
|
||||
continue
|
||||
if not _auth_isolated(services):
|
||||
continue
|
||||
|
||||
header_line = header_lines.get(index)
|
||||
if header_line is not None and is_suppressed_at(
|
||||
text_lines, header_line, _RULE
|
||||
):
|
||||
continue
|
||||
|
||||
missing = [
|
||||
key
|
||||
for key in sorted(non_auth_union)
|
||||
if not _entry_pinned_false(services.get(key))
|
||||
]
|
||||
|
||||
if missing:
|
||||
offenders.append(
|
||||
f"{role_name}: variant[{index}] is auth-isolated "
|
||||
f"but does not pin "
|
||||
f"{', '.join(missing)} to "
|
||||
f"`enabled: false, shared: false`. Either add the "
|
||||
f"explicit false declarations or mark the variant "
|
||||
f"with ``# nocheck: {_RULE}``."
|
||||
)
|
||||
|
||||
if offenders:
|
||||
self.fail(
|
||||
f"meta/variants.yml auth-isolated variants must pin every "
|
||||
f"non-auth service-key to literal false "
|
||||
f"({_RULE}, {len(offenders)} offender(s)):\n"
|
||||
+ "\n".join(f" - {o}" for o in offenders)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user