fix(relay): guard native plugin ownership cutover
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
This commit is contained in:
+30
-1
@@ -18,6 +18,10 @@ from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
|
||||
from hermes_constants import get_hermes_home
|
||||
from hermes_cli.relay_plugin_cutover import (
|
||||
RELAY_PLUGINS_CONFIG_ENV,
|
||||
configured_legacy_relay_env_vars,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -27,7 +31,6 @@ LOGICAL_LLM_SCOPE = "hermes.logical_llm_call"
|
||||
RUNTIME_SCHEMA_KEY = "hermes.relay.schema_version"
|
||||
RUNTIME_SCHEMA_VERSION = "hermes.relay.runtime.v1"
|
||||
RUNTIME_INSTANCE_KEY = "hermes.relay.runtime_instance"
|
||||
RELAY_PLUGINS_CONFIG_ENV = "HERMES_NEMO_RELAY_PLUGINS_TOML"
|
||||
RELAY_PLUGINS_EXECUTION_CONSUMER = "hermes.nemo_relay.plugins"
|
||||
_PROFILE_KEY_CACHE: dict[str, str] = {}
|
||||
|
||||
@@ -265,6 +268,23 @@ class _ProcessRelayPluginConfiguration:
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
existing_report = relay.plugin.report()
|
||||
except Exception:
|
||||
logger.warning(
|
||||
"Hermes could not determine whether a process-global Relay "
|
||||
"plugin configuration is already active; refusing to replace it",
|
||||
exc_info=True,
|
||||
)
|
||||
return False
|
||||
if existing_report is not None:
|
||||
logger.warning(
|
||||
"A process-global Relay plugin configuration is already active "
|
||||
"outside Hermes native ownership; leaving it unchanged and "
|
||||
"disabling Hermes-managed Relay middleware for this process"
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
configured_inputs = _configured_plugin_inputs(relay)
|
||||
if configured_inputs is None:
|
||||
@@ -1894,6 +1914,15 @@ def _configured_plugin_inputs(
|
||||
"""Load environment-selected plugin inputs, or leave plugins disabled."""
|
||||
configured = os.environ.get(RELAY_PLUGINS_CONFIG_ENV, "").strip()
|
||||
if not configured:
|
||||
legacy_vars = configured_legacy_relay_env_vars(os.environ)
|
||||
if legacy_vars:
|
||||
logger.warning(
|
||||
"Legacy NeMo Relay exporter variables are set but no %s was "
|
||||
"provided. %s no longer activate Relay exporters; migrate the "
|
||||
"exporter configuration to a Relay plugins.toml file.",
|
||||
RELAY_PLUGINS_CONFIG_ENV,
|
||||
", ".join(legacy_vars),
|
||||
)
|
||||
return None
|
||||
|
||||
config_path = Path(configured).expanduser()
|
||||
|
||||
@@ -9,6 +9,15 @@ Hermes execution remains available, while Relay scopes, middleware, plugins,
|
||||
and subscribers are unavailable. The `hermes-agent[nemo-relay]` extra remains
|
||||
as a no-op compatibility alias for existing installation commands.
|
||||
|
||||
> [!WARNING]
|
||||
> This removes the Hermes `observability/nemo_relay` plugin. Existing users
|
||||
> must remove `observability/nemo_relay` (or its legacy `nemo_relay` alias)
|
||||
> from `plugins.enabled` and move exporter configuration into a Relay
|
||||
> `plugins.toml` selected with `HERMES_NEMO_RELAY_PLUGINS_TOML`. The legacy
|
||||
> `HERMES_NEMO_RELAY_ATOF_*` and `HERMES_NEMO_RELAY_ATIF_*` variables no
|
||||
> longer activate exporters. Without the new variable, Hermes does not run
|
||||
> Relay plugin discovery, configuration layering, middleware, or exporters.
|
||||
|
||||
Hermes requires NeMo Relay 0.7.1 or later within the 0.7 release line. That
|
||||
release establishes the lossless provider-codec contract used for Anthropic
|
||||
Messages, OpenAI Chat Completions, and OpenAI Responses requests.
|
||||
|
||||
@@ -3557,7 +3557,7 @@ DEFAULT_CONFIG = {
|
||||
},
|
||||
|
||||
# Config schema version - bump this when adding new required fields
|
||||
"_config_version": 37,
|
||||
"_config_version": 38,
|
||||
}
|
||||
|
||||
# Optional environment variables that enhance functionality
|
||||
|
||||
@@ -815,6 +815,37 @@ def _migrate_to_37(results: Dict[str, Any], quiet: bool) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _migrate_to_38(results: Dict[str, Any], quiet: bool) -> None:
|
||||
# Version 37 → 38: the bundled observability/nemo_relay plugin was
|
||||
# removed when Relay lifecycle ownership moved into the agent core.
|
||||
_c = _cfg()
|
||||
read_raw_config = _c.read_raw_config
|
||||
_persist_migration = _c._persist_migration
|
||||
|
||||
from hermes_cli.relay_plugin_cutover import legacy_relay_plugin_keys
|
||||
|
||||
config = read_raw_config()
|
||||
plugins = config.get("plugins")
|
||||
if not isinstance(plugins, dict):
|
||||
return
|
||||
enabled = plugins.get("enabled")
|
||||
removed = legacy_relay_plugin_keys(enabled)
|
||||
if not removed or not isinstance(enabled, list):
|
||||
return
|
||||
|
||||
plugins["enabled"] = [value for value in enabled if value not in removed]
|
||||
config["plugins"] = plugins
|
||||
_persist_migration(config)
|
||||
message = (
|
||||
"Removed legacy Relay plugin from plugins.enabled: "
|
||||
f"{', '.join(removed)}. Configure native Relay plugins with "
|
||||
"HERMES_NEMO_RELAY_PLUGINS_TOML."
|
||||
)
|
||||
results["warnings"].append(message)
|
||||
if not quiet:
|
||||
print(f" ⚠ {message}")
|
||||
|
||||
|
||||
#: Registry of (target_version, migration_fn), strictly ascending. The driver
|
||||
#: applies every entry whose target version is greater than the on-disk
|
||||
#: observe earlier steps' writes via read_raw_config() (filesystem state).
|
||||
@@ -839,6 +870,7 @@ MIGRATIONS: Tuple[Tuple[int, Callable[[Dict[str, Any], bool], None]], ...] = (
|
||||
(35, _migrate_to_35),
|
||||
(36, _migrate_to_36),
|
||||
(37, _migrate_to_37),
|
||||
(38, _migrate_to_38),
|
||||
)
|
||||
|
||||
|
||||
|
||||
+51
-3
@@ -525,6 +525,46 @@ def collect_deprecated_env_vars(env_map: dict | None) -> list[tuple[str, str]]:
|
||||
return findings
|
||||
|
||||
|
||||
def collect_relay_plugin_cutover_findings(
|
||||
raw_config: dict | None,
|
||||
env_map: dict | None,
|
||||
) -> list[tuple[str, str]]:
|
||||
"""Return actionable findings for the removed Hermes Relay plugin."""
|
||||
from hermes_cli.relay_plugin_cutover import (
|
||||
LEGACY_RELAY_EXPORT_ENV_VARS,
|
||||
RELAY_PLUGINS_CONFIG_ENV,
|
||||
configured_legacy_relay_env_vars,
|
||||
legacy_relay_plugin_keys,
|
||||
)
|
||||
|
||||
findings: list[tuple[str, str]] = []
|
||||
if isinstance(raw_config, dict):
|
||||
plugins = raw_config.get("plugins")
|
||||
if isinstance(plugins, dict):
|
||||
for key in legacy_relay_plugin_keys(plugins.get("enabled")):
|
||||
findings.append(
|
||||
(
|
||||
f"plugins.enabled: {key}",
|
||||
f"remove it and configure {RELAY_PLUGINS_CONFIG_ENV}",
|
||||
)
|
||||
)
|
||||
|
||||
effective_env = dict(env_map or {})
|
||||
for name in (*LEGACY_RELAY_EXPORT_ENV_VARS, RELAY_PLUGINS_CONFIG_ENV):
|
||||
if name not in effective_env and os.environ.get(name) is not None:
|
||||
effective_env[name] = os.environ[name]
|
||||
if not str(effective_env.get(RELAY_PLUGINS_CONFIG_ENV, "")).strip():
|
||||
for name in configured_legacy_relay_env_vars(effective_env):
|
||||
findings.append(
|
||||
(
|
||||
name,
|
||||
f"move exporter settings to {RELAY_PLUGINS_CONFIG_ENV}; "
|
||||
"this variable is now ignored",
|
||||
)
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
def report_deprecated_config_and_env(
|
||||
raw_config: dict | None = None,
|
||||
env_map: dict | None = None,
|
||||
@@ -535,18 +575,26 @@ def report_deprecated_config_and_env(
|
||||
(empty when nothing deprecated is present). Does not mutate config/env and
|
||||
does not append to the blocking ``issues`` list.
|
||||
"""
|
||||
findings = collect_deprecated_config_keys(raw_config)
|
||||
findings.extend(collect_deprecated_env_vars(env_map))
|
||||
deprecated = collect_deprecated_config_keys(raw_config)
|
||||
deprecated.extend(collect_deprecated_env_vars(env_map))
|
||||
relay_cutover = collect_relay_plugin_cutover_findings(raw_config, env_map)
|
||||
findings = deprecated + relay_cutover
|
||||
if not findings:
|
||||
check_ok("No deprecated config keys or env vars")
|
||||
return findings
|
||||
|
||||
for legacy, replacement in findings:
|
||||
for legacy, replacement in deprecated:
|
||||
check_warn(
|
||||
f"Deprecated: {legacy}",
|
||||
f"(use {replacement} instead)",
|
||||
)
|
||||
check_info(f"Replace {legacy} → {replacement} (warn-only; not auto-migrated here)")
|
||||
for legacy, replacement in relay_cutover:
|
||||
check_warn(
|
||||
f"Breaking Relay migration: {legacy}",
|
||||
f"({replacement})",
|
||||
)
|
||||
check_info(f"Migrate {legacy}: {replacement}")
|
||||
return findings
|
||||
|
||||
|
||||
|
||||
@@ -71,6 +71,11 @@ from hermes_cli.plugin_capabilities import ( # noqa: F401 — re-exported
|
||||
from hermes_cli.plugin_capabilities import (
|
||||
parse_declared_capabilities as _parse_declared_capabilities,
|
||||
)
|
||||
from hermes_cli.relay_plugin_cutover import (
|
||||
LEGACY_RELAY_PLUGIN_KEYS,
|
||||
RELAY_PLUGINS_CONFIG_ENV,
|
||||
legacy_relay_plugin_keys,
|
||||
)
|
||||
|
||||
|
||||
def get_bundled_plugins_dir() -> Path:
|
||||
@@ -3892,6 +3897,14 @@ class PluginManager:
|
||||
# don't collide even when both manifests say ``name: openai``.
|
||||
disabled = _get_disabled_plugins()
|
||||
enabled = _get_enabled_plugins() # None = opt-in default (nothing enabled)
|
||||
stale_relay_keys = legacy_relay_plugin_keys(enabled)
|
||||
if stale_relay_keys:
|
||||
logger.warning(
|
||||
"Removed Hermes plugin %s is still listed in plugins.enabled; "
|
||||
"remove it and configure native Relay plugins with %s",
|
||||
", ".join(stale_relay_keys),
|
||||
RELAY_PLUGINS_CONFIG_ENV,
|
||||
)
|
||||
winners: Dict[str, PluginManifest] = {}
|
||||
for manifest in manifests:
|
||||
winners[manifest.key or manifest.name] = manifest
|
||||
@@ -3902,6 +3915,26 @@ class PluginManager:
|
||||
for manifest in winners.values():
|
||||
lookup_key = manifest.key or manifest.name
|
||||
|
||||
# Relay lifecycle ownership now lives in the Hermes core. Loading
|
||||
# an old user or entry-point copy would let plugin.initialize()
|
||||
# compete for the same process-global Relay registries.
|
||||
if (
|
||||
lookup_key in LEGACY_RELAY_PLUGIN_KEYS
|
||||
or manifest.name in LEGACY_RELAY_PLUGIN_KEYS
|
||||
):
|
||||
loaded = LoadedPlugin(manifest=manifest, enabled=False)
|
||||
loaded.error = (
|
||||
"removed — Relay lifecycle is owned by Hermes core; configure "
|
||||
f"{RELAY_PLUGINS_CONFIG_ENV} instead"
|
||||
)
|
||||
self._plugins[lookup_key] = loaded
|
||||
logger.warning(
|
||||
"Refusing to load removed Hermes Relay plugin '%s'; %s",
|
||||
lookup_key,
|
||||
loaded.error,
|
||||
)
|
||||
continue
|
||||
|
||||
# Explicit disable always wins (matches on key or on legacy
|
||||
# bare name for back-compat with existing user configs).
|
||||
if lookup_key in disabled or manifest.name in disabled:
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
"""Shared migration guards for Hermes' native NeMo Relay ownership."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
|
||||
RELAY_PLUGINS_CONFIG_ENV = "HERMES_NEMO_RELAY_PLUGINS_TOML"
|
||||
|
||||
LEGACY_RELAY_PLUGIN_KEYS = frozenset(
|
||||
{
|
||||
"nemo_relay",
|
||||
"observability/nemo_relay",
|
||||
}
|
||||
)
|
||||
|
||||
LEGACY_RELAY_EXPORT_ENV_VARS = frozenset(
|
||||
{
|
||||
"HERMES_NEMO_RELAY_ATOF_ENABLED",
|
||||
"HERMES_NEMO_RELAY_ATOF_OUTPUT_DIRECTORY",
|
||||
"HERMES_NEMO_RELAY_ATOF_FILENAME",
|
||||
"HERMES_NEMO_RELAY_ATOF_MODE",
|
||||
"HERMES_NEMO_RELAY_ATIF_ENABLED",
|
||||
"HERMES_NEMO_RELAY_ATIF_OUTPUT_DIRECTORY",
|
||||
"HERMES_NEMO_RELAY_ATIF_FILENAME_TEMPLATE",
|
||||
"HERMES_NEMO_RELAY_ATIF_AGENT_NAME",
|
||||
"HERMES_NEMO_RELAY_ATIF_AGENT_VERSION",
|
||||
"HERMES_NEMO_RELAY_ATIF_MODEL_NAME",
|
||||
"HERMES_NEMO_RELAY_ATIF_SUBAGENT_EXPORT_MODE",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def legacy_relay_plugin_keys(values: Any) -> tuple[str, ...]:
|
||||
"""Return removed Relay plugin identities present in a config value."""
|
||||
if not isinstance(values, (list, tuple, set, frozenset)):
|
||||
return ()
|
||||
return tuple(
|
||||
sorted(
|
||||
{
|
||||
value
|
||||
for value in values
|
||||
if isinstance(value, str) and value in LEGACY_RELAY_PLUGIN_KEYS
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def configured_legacy_relay_env_vars(
|
||||
env: Mapping[str, Any] | None,
|
||||
) -> tuple[str, ...]:
|
||||
"""Return non-empty legacy Relay exporter variables in *env*."""
|
||||
if env is None:
|
||||
return ()
|
||||
return tuple(
|
||||
sorted(
|
||||
name
|
||||
for name in LEGACY_RELAY_EXPORT_ENV_VARS
|
||||
if env.get(name) is not None and str(env[name]).strip()
|
||||
)
|
||||
)
|
||||
@@ -20,11 +20,15 @@ class _FakeRelay:
|
||||
initialize_error: Exception | None = None,
|
||||
dynamic_initialize_error: Exception | None = None,
|
||||
activation_close_error: Exception | None = None,
|
||||
active_report: Any = None,
|
||||
report_error: Exception | None = None,
|
||||
) -> None:
|
||||
self.events: list[tuple[Any, ...]] = []
|
||||
self.initialize_error = initialize_error
|
||||
self.dynamic_initialize_error = dynamic_initialize_error
|
||||
self.activation_close_error = activation_close_error
|
||||
self.active_report = active_report
|
||||
self.report_error = report_error
|
||||
self.dynamic_plugin_specs: list[dict[str, Any]] = []
|
||||
self.ScopeType = SimpleNamespace(Agent="agent")
|
||||
self.plugin = SimpleNamespace(
|
||||
@@ -32,6 +36,7 @@ class _FakeRelay:
|
||||
initialize_with_dynamic_plugins=self._initialize_dynamic_plugins,
|
||||
load_dynamic_plugin_activation_specs=self._load_dynamic_plugin_specs,
|
||||
clear_async=self._clear_plugins_async,
|
||||
report=self._report_plugins,
|
||||
)
|
||||
self.scope = SimpleNamespace(
|
||||
push=self._scope_push,
|
||||
@@ -74,6 +79,11 @@ class _FakeRelay:
|
||||
async def _clear_plugins_async(self) -> None:
|
||||
self.events.append(("plugin.clear_async",))
|
||||
|
||||
def _report_plugins(self) -> Any:
|
||||
if self.report_error is not None:
|
||||
raise self.report_error
|
||||
return self.active_report
|
||||
|
||||
def _scope_push(self, name: str, scope_type: Any, **kwargs: Any) -> Any:
|
||||
handle = ("scope", name, len(self.events))
|
||||
self.events.append(("scope.push", name, scope_type, kwargs))
|
||||
@@ -142,6 +152,63 @@ def test_relay_initializes_explicit_plugins_before_first_session_scope(
|
||||
host.shutdown()
|
||||
|
||||
|
||||
def test_foreign_active_plugin_configuration_is_left_unchanged(
|
||||
explicit_static_config,
|
||||
caplog,
|
||||
):
|
||||
foreign_report = {"diagnostics": [], "source": "embedding-host"}
|
||||
relay = _FakeRelay(active_report=foreign_report)
|
||||
|
||||
with caplog.at_level("WARNING"):
|
||||
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
|
||||
|
||||
try:
|
||||
assert not host.managed_execution_enabled()
|
||||
assert relay.active_report is foreign_report
|
||||
assert relay.events == []
|
||||
assert "already active outside Hermes native ownership" in caplog.text
|
||||
assert "leaving it unchanged" in caplog.text
|
||||
finally:
|
||||
host.shutdown()
|
||||
|
||||
|
||||
def test_unreadable_foreign_plugin_state_fails_safe(
|
||||
explicit_static_config,
|
||||
caplog,
|
||||
):
|
||||
relay = _FakeRelay(report_error=RuntimeError("report unavailable"))
|
||||
|
||||
with caplog.at_level("WARNING"):
|
||||
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
|
||||
|
||||
try:
|
||||
assert not host.managed_execution_enabled()
|
||||
assert relay.events == []
|
||||
assert "refusing to replace it" in caplog.text
|
||||
finally:
|
||||
host.shutdown()
|
||||
|
||||
|
||||
def test_legacy_exporter_env_without_plugins_toml_warns_and_stays_disabled(
|
||||
monkeypatch,
|
||||
caplog,
|
||||
):
|
||||
monkeypatch.delenv(relay_runtime.RELAY_PLUGINS_CONFIG_ENV, raising=False)
|
||||
monkeypatch.setenv("HERMES_NEMO_RELAY_ATOF_ENABLED", "1")
|
||||
relay = _FakeRelay()
|
||||
|
||||
with caplog.at_level("WARNING"):
|
||||
host = relay_runtime.RelayRuntime(relay=relay, profile_key="profile")
|
||||
|
||||
try:
|
||||
assert not host.managed_execution_enabled()
|
||||
assert relay.events == []
|
||||
assert "no HERMES_NEMO_RELAY_PLUGINS_TOML was provided" in caplog.text
|
||||
assert "HERMES_NEMO_RELAY_ATOF_ENABLED" in caplog.text
|
||||
finally:
|
||||
host.shutdown()
|
||||
|
||||
|
||||
def test_initialization_failure_is_fail_open(explicit_static_config, caplog):
|
||||
relay = _FakeRelay(initialize_error=RuntimeError("rejected config"))
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from hermes_cli.plugins import (
|
||||
resolve_plugin_command_result,
|
||||
_portable_skill_namespace,
|
||||
)
|
||||
from hermes_cli.relay_plugin_cutover import RELAY_PLUGINS_CONFIG_ENV
|
||||
from hermes_cli.middleware import (
|
||||
VALID_MIDDLEWARE,
|
||||
apply_llm_request_middleware,
|
||||
@@ -120,6 +121,43 @@ def _make_plugin_dir(base: Path, name: str, *, register_body: str = "pass",
|
||||
class TestPluginDiscovery:
|
||||
"""Tests for plugin discovery from directories and entry points."""
|
||||
|
||||
def test_removed_relay_plugin_identity_cannot_be_reloaded(
|
||||
self, monkeypatch, caplog
|
||||
):
|
||||
from hermes_cli import plugins as plugins_mod
|
||||
|
||||
manifest = PluginManifest(
|
||||
name="nemo_relay",
|
||||
key="observability/nemo_relay",
|
||||
source="user",
|
||||
)
|
||||
manager = PluginManager()
|
||||
monkeypatch.setattr(
|
||||
manager,
|
||||
"_collect_directory_manifests",
|
||||
lambda: [manifest],
|
||||
)
|
||||
monkeypatch.setattr(manager, "_scan_entry_points", lambda: [])
|
||||
monkeypatch.setattr(
|
||||
plugins_mod,
|
||||
"_get_enabled_plugins",
|
||||
lambda: {"observability/nemo_relay"},
|
||||
)
|
||||
monkeypatch.setattr(plugins_mod, "_get_disabled_plugins", lambda: set())
|
||||
loaded: list[PluginManifest] = []
|
||||
monkeypatch.setattr(manager, "_load_plugin", loaded.append)
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
manager.discover_and_load()
|
||||
|
||||
state = manager._plugins["observability/nemo_relay"]
|
||||
assert loaded == []
|
||||
assert not state.enabled
|
||||
assert state.error is not None
|
||||
assert "Relay lifecycle is owned by Hermes core" in state.error
|
||||
assert RELAY_PLUGINS_CONFIG_ENV in state.error
|
||||
assert "Refusing to load removed Hermes Relay plugin" in caplog.text
|
||||
|
||||
def test_enabled_portable_plugin_registers_components(
|
||||
self, tmp_path, monkeypatch
|
||||
):
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"""Regression tests for migration from the removed Hermes Relay plugin."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
import yaml
|
||||
|
||||
from hermes_cli.config import migrate_config
|
||||
from hermes_cli.doctor import collect_relay_plugin_cutover_findings
|
||||
from hermes_cli.relay_plugin_cutover import RELAY_PLUGINS_CONFIG_ENV
|
||||
|
||||
|
||||
def test_v38_migration_removes_only_legacy_relay_plugin_keys(tmp_path):
|
||||
config_path = tmp_path / "config.yaml"
|
||||
config_path.write_text(
|
||||
yaml.safe_dump(
|
||||
{
|
||||
"_config_version": 37,
|
||||
"plugins": {
|
||||
"enabled": [
|
||||
"keep-me",
|
||||
"observability/nemo_relay",
|
||||
"nemo_relay",
|
||||
]
|
||||
},
|
||||
},
|
||||
sort_keys=False,
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
with patch.dict(os.environ, {"HERMES_HOME": str(tmp_path)}):
|
||||
results = migrate_config(interactive=False, quiet=True)
|
||||
|
||||
raw = yaml.safe_load(config_path.read_text(encoding="utf-8"))
|
||||
assert raw["_config_version"] == 38
|
||||
assert raw["plugins"]["enabled"] == ["keep-me"]
|
||||
assert any(
|
||||
"observability/nemo_relay" in warning
|
||||
and RELAY_PLUGINS_CONFIG_ENV in warning
|
||||
for warning in results["warnings"]
|
||||
)
|
||||
|
||||
|
||||
def test_doctor_reports_stale_relay_plugin_key():
|
||||
findings = dict(
|
||||
collect_relay_plugin_cutover_findings(
|
||||
{"plugins": {"enabled": ["observability/nemo_relay"]}},
|
||||
{},
|
||||
)
|
||||
)
|
||||
|
||||
replacement = findings["plugins.enabled: observability/nemo_relay"]
|
||||
assert "remove it" in replacement
|
||||
assert RELAY_PLUGINS_CONFIG_ENV in replacement
|
||||
|
||||
|
||||
def test_doctor_reports_legacy_exporter_env_without_new_config(monkeypatch):
|
||||
monkeypatch.delenv(RELAY_PLUGINS_CONFIG_ENV, raising=False)
|
||||
findings = dict(
|
||||
collect_relay_plugin_cutover_findings(
|
||||
{},
|
||||
{"HERMES_NEMO_RELAY_ATIF_ENABLED": "true"},
|
||||
)
|
||||
)
|
||||
|
||||
assert "now ignored" in findings["HERMES_NEMO_RELAY_ATIF_ENABLED"]
|
||||
assert RELAY_PLUGINS_CONFIG_ENV in findings["HERMES_NEMO_RELAY_ATIF_ENABLED"]
|
||||
|
||||
|
||||
def test_doctor_does_not_warn_for_legacy_env_after_new_config_is_selected(
|
||||
monkeypatch,
|
||||
):
|
||||
monkeypatch.delenv(RELAY_PLUGINS_CONFIG_ENV, raising=False)
|
||||
findings = collect_relay_plugin_cutover_findings(
|
||||
{},
|
||||
{
|
||||
RELAY_PLUGINS_CONFIG_ENV: "/tmp/plugins.toml",
|
||||
"HERMES_NEMO_RELAY_ATIF_ENABLED": "true",
|
||||
},
|
||||
)
|
||||
|
||||
assert findings == []
|
||||
Reference in New Issue
Block a user