From 0d589ed673351fe83a4d49be74e4b01c9f746797 Mon Sep 17 00:00:00 2001 From: Sudesh0119 Date: Tue, 27 Oct 2026 14:37:00 +0530 Subject: [PATCH 1/5] fix: ADDON-91029 scope observability app.conf lookup to the owning add-on The unscoped app.conf lookup merged every installed app, so telemetry could be labelled with another add-on's name and version. Detect the bundling app from solnlib's install path and scope the lookup to it. No public API or method signature changes. Co-Authored-By: Claude Code --- solnlib/observability.py | 42 +++++++----- solnlib/splunkenv.py | 26 ++++++++ tests/unit/test_observability.py | 107 +++++++++++++++++++++++++++++++ tests/unit/test_splunkenv.py | 22 +++++++ 4 files changed, 181 insertions(+), 16 deletions(-) diff --git a/solnlib/observability.py b/solnlib/observability.py index a8249e1f..8f631281 100644 --- a/solnlib/observability.py +++ b/solnlib/observability.py @@ -53,7 +53,7 @@ import urllib.request from typing import Callable, ClassVar, Optional, Union from ._observability_flush import _register_controller -from .splunkenv import get_conf_stanzas +from .splunkenv import _get_app_name_from_path, get_conf_stanzas from opentelemetry.metrics import Instrument, Meter from opentelemetry.sdk.metrics import MeterProvider, Counter, Histogram from opentelemetry.sdk.metrics.export import ( @@ -392,12 +392,13 @@ def __init__( logger: Python logger (or ``LoggerAdapter``) for all diagnostic output. Typically the caller's own module-level logger. ta_name: Add-on identifier, e.g. ``"Splunk_TA_myapp"``. When - *None* the value is read from the ``[id]`` stanza of - ``app.conf`` via :func:`~solnlib.splunkenv.get_conf_stanzas`. - Pass it explicitly when the add-on runs outside a full Splunk - environment or to avoid the ``app.conf`` lookup. + *None* the value is read from the ``[id]`` stanza of the + ``app.conf`` of the app that bundles solnlib (detected from + the install path, falling back to the app directory name). + Pass it explicitly when the add-on runs outside a standard + ``etc/apps/`` layout or to avoid the ``app.conf`` lookup. ta_version: Add-on version string, e.g. ``"3.1.0"``. When *None* - the value is read from the ``[launcher]`` stanza of + the value is read from the ``[launcher]`` stanza of the same ``app.conf``. Falls back to ``"unknown"`` if it cannot be determined. extra_exporters: Optional list of additional @@ -504,19 +505,28 @@ def __init__( ) def _read_ta_info(self) -> tuple[Optional[str], Optional[str]]: - """Read the add-on name and version from ``app.conf``. + """Read the add-on name and version from the owning app's ``app.conf``. - Returns a ``(ta_name, ta_version)`` tuple. Either value is - ``None`` when the corresponding key is missing or when - ``app.conf`` cannot be read (e.g. outside a Splunk environment). + The owning app is the one that bundles solnlib, detected from the + install path (``etc/apps//...``). The lookup is always scoped to + that app: an unscoped ``app.conf`` merge can return another installed + add-on's ``[id]`` stanza. + + Returns a ``(ta_name, ta_version)`` tuple. Both are ``None`` when the + owning app cannot be detected or ``app.conf`` cannot be read; the + version alone is ``None`` when ``[launcher] version`` is missing. """ - try: - stanzas = get_conf_stanzas("app") - ta_name = stanzas.get("id", {}).get("name") or None - scoped_stanzas = ( - get_conf_stanzas("app", app_name=ta_name) if ta_name else stanzas + app_name = _get_app_name_from_path() + if not app_name: + self._logger.info( + "Could not determine the add-on that bundles solnlib from its " + "install path; pass ta_name explicitly to enable telemetry" ) - ta_version = scoped_stanzas.get("launcher", {}).get("version") or None + return None, None + try: + stanzas = get_conf_stanzas("app", app_name=app_name) + ta_name = stanzas.get("id", {}).get("name") or app_name + ta_version = stanzas.get("launcher", {}).get("version") or None return ta_name, ta_version except Exception as error: self._logger.info( diff --git a/solnlib/splunkenv.py b/solnlib/splunkenv.py index dae14af4..6d20c50a 100644 --- a/solnlib/splunkenv.py +++ b/solnlib/splunkenv.py @@ -22,6 +22,7 @@ import json from configparser import ConfigParser from io import StringIO +from pathlib import Path from typing import Optional, Union import __main__ from solnlib._settings import use_btool @@ -66,6 +67,7 @@ def msp(*args, **kwargs): ETC_LEAF = "etc" APP_SYSTEM = "system" APP_HEC = "splunk_httpinput" +_APPS_DIRS = ("apps", "peer-apps", "slave-apps") class SessionKeyNotFound(Exception): @@ -92,6 +94,30 @@ def make_splunkhome_path(parts: Union[list, tuple]) -> str: return msp(parts) +def _get_app_name_from_path(path: Optional[str] = None) -> Optional[str]: + """Return the name of the Splunk app that contains `path`. + + The app is the directory directly under ``$SPLUNK_HOME/etc/apps`` + (or ``etc/peer-apps`` / ``etc/slave-apps`` on indexer cluster peers). + For example, ``$SPLUNK_HOME/etc/apps/Splunk_TA_test/lib/solnlib/splunkenv.py`` + resolves to ``"Splunk_TA_test"``. + + Arguments: + path: File system path to inspect. Defaults to the location of this + module, i.e. the app that bundles solnlib. + + Returns: + App name, or None if `path` is not inside a Splunk apps directory. + """ + # abspath, not resolve(): apps symlinked into etc/apps must keep their name. + resolved = Path(op.abspath(path or __file__)) + for candidate in (resolved, *resolved.parents): + parent = candidate.parent + if parent.name in _APPS_DIRS and parent.parent.name == ETC_LEAF: + return candidate.name + return None + + def get_splunk_host_info(session_key: Optional[str] = None) -> tuple: """Get splunk host info. diff --git a/tests/unit/test_observability.py b/tests/unit/test_observability.py index fd53a367..86fefc30 100644 --- a/tests/unit/test_observability.py +++ b/tests/unit/test_observability.py @@ -765,6 +765,9 @@ def test_register_instrument_calls_callback(self, logger, monkeypatch): def test_read_ta_info_returns_values(self, logger, monkeypatch): # Arrange + monkeypatch.setattr( + "solnlib.observability._get_app_name_from_path", lambda: "my_ta" + ) monkeypatch.setattr( "solnlib.observability.get_conf_stanzas", lambda conf, **kwargs: { @@ -781,8 +784,109 @@ def test_read_ta_info_returns_values(self, logger, monkeypatch): # Assert assert svc._meter is not None + def test_read_ta_info_always_scoped_to_owning_app(self, logger, monkeypatch): + # Arrange + monkeypatch.setattr( + "solnlib.observability._get_app_name_from_path", + lambda: "Splunk_TA_google-cloudplatform", + ) + get_conf = MagicMock( + return_value={ + "id": {"name": "Splunk_TA_google-cloudplatform"}, + "launcher": {"version": "5.0.0"}, + } + ) + monkeypatch.setattr("solnlib.observability.get_conf_stanzas", get_conf) + svc = _make_service(logger, monkeypatch) + # Act + result = svc._read_ta_info() + # Assert + assert result == ("Splunk_TA_google-cloudplatform", "5.0.0") + get_conf.assert_called_once_with( + "app", app_name="Splunk_TA_google-cloudplatform" + ) + + def test_read_ta_info_reports_owning_app_when_other_apps_installed( + self, logger, monkeypatch + ): + # Arrange: an unscoped lookup would return Splunk_TA_cyberark's [id]. + apps = { + "Splunk_TA_cyberark": { + "id": {"name": "Splunk_TA_cyberark"}, + "launcher": {"version": "1.2.0"}, + }, + "Splunk_TA_google-cloudplatform": { + "id": {"name": "Splunk_TA_google-cloudplatform"}, + "launcher": {"version": "5.0.0"}, + }, + } + + def fake_get_conf_stanzas(conf, app_name=None): + if app_name is None: + return apps["Splunk_TA_cyberark"] + return apps[app_name] + + monkeypatch.setattr( + "solnlib.observability.get_conf_stanzas", fake_get_conf_stanzas + ) + monkeypatch.setattr( + "solnlib.observability._get_app_name_from_path", + lambda: "Splunk_TA_google-cloudplatform", + ) + svc = _make_service(logger, monkeypatch) + # Act / Assert + assert svc._read_ta_info() == ("Splunk_TA_google-cloudplatform", "5.0.0") + + def test_read_ta_info_missing_id_falls_back_to_app_dir(self, logger, monkeypatch): + # Arrange + monkeypatch.setattr( + "solnlib.observability._get_app_name_from_path", lambda: "Splunk_TA_aws" + ) + monkeypatch.setattr( + "solnlib.observability.get_conf_stanzas", + lambda conf, **kwargs: {"launcher": {"version": "7.0.0"}}, + ) + svc = _make_service(logger, monkeypatch) + # Act / Assert + assert svc._read_ta_info() == ("Splunk_TA_aws", "7.0.0") + + def test_read_ta_info_no_detectable_app_returns_none(self, logger, monkeypatch): + # Arrange + monkeypatch.setattr( + "solnlib.observability._get_app_name_from_path", lambda: None + ) + get_conf = MagicMock() + monkeypatch.setattr("solnlib.observability.get_conf_stanzas", get_conf) + svc = _make_service(logger, monkeypatch) + logger.reset_mock() + # Act + result = svc._read_ta_info() + # Assert + assert result == (None, None) + get_conf.assert_not_called() + logger.info.assert_called() + logger.warning.assert_not_called() + + def test_no_detectable_app_skips_telemetry(self, logger, monkeypatch): + # Arrange + monkeypatch.setattr( + "solnlib.observability._get_app_name_from_path", lambda: None + ) + monkeypatch.setattr( + "solnlib.observability.ObservabilityService._create_otlp_exporter", + lambda self: None, + ) + # Act + svc = ObservabilityService(modinput_type="test-input", logger=logger) + # Assert + assert svc._meter is None + logger.warning.assert_not_called() + def test_read_ta_info_handles_exception(self, logger, monkeypatch): # Arrange + monkeypatch.setattr( + "solnlib.observability._get_app_name_from_path", lambda: "my_ta" + ) monkeypatch.setattr( "solnlib.observability.ObservabilityService._create_otlp_exporter", lambda self: None, @@ -1516,6 +1620,9 @@ def test_read_ta_info_exception_uses_safe_str(self, logger, monkeypatch): monkeypatch.setattr( "solnlib.observability._safe_exception_str", lambda error: "SAFE" ) + monkeypatch.setattr( + "solnlib.observability._get_app_name_from_path", lambda: "my_ta" + ) monkeypatch.setattr( "solnlib.observability.ObservabilityService._create_otlp_exporter", lambda self: None, diff --git a/tests/unit/test_splunkenv.py b/tests/unit/test_splunkenv.py index 9580d285..11905035 100644 --- a/tests/unit/test_splunkenv.py +++ b/tests/unit/test_splunkenv.py @@ -164,3 +164,25 @@ def test_splunkenv_used_outside_splunk_env(): "server", "app", session_key="session_key" ) assert exc_info.value.args[0] == "This module requires Splunk to be installed." + + +@pytest.mark.parametrize( + "rel_path,expected", + [ + ("etc/apps/Splunk_TA_aws/lib/solnlib/splunkenv.py", "Splunk_TA_aws"), + ("etc/apps/Splunk_TA_aws/bin/lib/solnlib/splunkenv.py", "Splunk_TA_aws"), + ("etc/peer-apps/Splunk_TA_aws/lib/solnlib/splunkenv.py", "Splunk_TA_aws"), + ("etc/slave-apps/Splunk_TA_aws/lib/solnlib/splunkenv.py", "Splunk_TA_aws"), + ("etc/apps/Splunk_TA_aws", "Splunk_TA_aws"), + ("etc/apps", None), + ("src/apps/Splunk_TA_aws/lib/solnlib/splunkenv.py", None), + ("checkout/solnlib/splunkenv.py", None), + ], +) +def test_get_app_name_from_path(tmp_path, rel_path, expected): + assert splunkenv._get_app_name_from_path(str(tmp_path / rel_path)) == expected + + +def test_get_app_name_from_path_defaults_to_module_location(): + # The repo checkout is not under etc/apps. + assert splunkenv._get_app_name_from_path() is None From 09f02cd26caf9999542730be6c23c0c0b12fb608 Mon Sep 17 00:00:00 2001 From: Sudesh0119 Date: Tue, 27 Oct 2026 20:39:08 +0530 Subject: [PATCH 2/5] fix: detect the running add-on from the main script path solnlib's own __file__ is already symlink-resolved (sys.path[0] is realpath'd), and SPLUNK_ETC may be relocated. Detect the app from the main script first, fall back to solnlib's path, and accept SPLUNK_ETC. Co-Authored-By: Claude Code --- solnlib/observability.py | 22 ++++++----- solnlib/splunkenv.py | 46 ++++++++++++++++++----- tests/unit/test_observability.py | 20 ++++------ tests/unit/test_splunkenv.py | 64 ++++++++++++++++++++++++++++++-- 4 files changed, 116 insertions(+), 36 deletions(-) diff --git a/solnlib/observability.py b/solnlib/observability.py index 8f631281..5330b883 100644 --- a/solnlib/observability.py +++ b/solnlib/observability.py @@ -53,7 +53,7 @@ import urllib.request from typing import Callable, ClassVar, Optional, Union from ._observability_flush import _register_controller -from .splunkenv import _get_app_name_from_path, get_conf_stanzas +from .splunkenv import _get_running_app_name, get_conf_stanzas from opentelemetry.metrics import Instrument, Meter from opentelemetry.sdk.metrics import MeterProvider, Counter, Histogram from opentelemetry.sdk.metrics.export import ( @@ -393,8 +393,9 @@ def __init__( output. Typically the caller's own module-level logger. ta_name: Add-on identifier, e.g. ``"Splunk_TA_myapp"``. When *None* the value is read from the ``[id]`` stanza of the - ``app.conf`` of the app that bundles solnlib (detected from - the install path, falling back to the app directory name). + ``app.conf`` of the running app (detected from the main + script's path, then solnlib's own path; falling back to the + app directory name). Pass it explicitly when the add-on runs outside a standard ``etc/apps/`` layout or to avoid the ``app.conf`` lookup. ta_version: Add-on version string, e.g. ``"3.1.0"``. When *None* @@ -507,20 +508,21 @@ def __init__( def _read_ta_info(self) -> tuple[Optional[str], Optional[str]]: """Read the add-on name and version from the owning app's ``app.conf``. - The owning app is the one that bundles solnlib, detected from the - install path (``etc/apps//...``). The lookup is always scoped to - that app: an unscoped ``app.conf`` merge can return another installed - add-on's ``[id]`` stanza. + The owning app is the one whose script is running + (``etc/apps//bin/...``), or failing that the one that bundles + solnlib. The lookup is always scoped to that app: an unscoped + ``app.conf`` merge can return another installed add-on's ``[id]`` + stanza. Returns a ``(ta_name, ta_version)`` tuple. Both are ``None`` when the owning app cannot be detected or ``app.conf`` cannot be read; the version alone is ``None`` when ``[launcher] version`` is missing. """ - app_name = _get_app_name_from_path() + app_name = _get_running_app_name() if not app_name: self._logger.info( - "Could not determine the add-on that bundles solnlib from its " - "install path; pass ta_name explicitly to enable telemetry" + "Could not determine the running add-on from the script or " + "solnlib path; pass ta_name explicitly to enable telemetry" ) return None, None try: diff --git a/solnlib/splunkenv.py b/solnlib/splunkenv.py index 6d20c50a..3b12d8dd 100644 --- a/solnlib/splunkenv.py +++ b/solnlib/splunkenv.py @@ -19,6 +19,7 @@ import os.path as op import socket import subprocess +import sys import json from configparser import ConfigParser from io import StringIO @@ -94,30 +95,55 @@ def make_splunkhome_path(parts: Union[list, tuple]) -> str: return msp(parts) -def _get_app_name_from_path(path: Optional[str] = None) -> Optional[str]: +def _is_splunk_etc_dir(path: Path) -> bool: + if path.name == ETC_LEAF: + return True + splunk_etc = os.environ.get("SPLUNK_ETC") + return bool(splunk_etc) and op.realpath(splunk_etc) == op.realpath(path) + + +def _get_app_name_from_path(path: str) -> Optional[str]: """Return the name of the Splunk app that contains `path`. - The app is the directory directly under ``$SPLUNK_HOME/etc/apps`` - (or ``etc/peer-apps`` / ``etc/slave-apps`` on indexer cluster peers). - For example, ``$SPLUNK_HOME/etc/apps/Splunk_TA_test/lib/solnlib/splunkenv.py`` - resolves to ``"Splunk_TA_test"``. + The app is the directory directly under ``$SPLUNK_ETC/apps`` (or + ``peer-apps`` / ``slave-apps`` on indexer cluster peers). For example, + ``$SPLUNK_HOME/etc/apps/Splunk_TA_test/bin/input.py`` resolves to + ``"Splunk_TA_test"``. Arguments: - path: File system path to inspect. Defaults to the location of this - module, i.e. the app that bundles solnlib. + path: File system path to inspect. Returns: App name, or None if `path` is not inside a Splunk apps directory. """ # abspath, not resolve(): apps symlinked into etc/apps must keep their name. - resolved = Path(op.abspath(path or __file__)) - for candidate in (resolved, *resolved.parents): + absolute = Path(op.abspath(path)) + for candidate in (absolute, *absolute.parents): parent = candidate.parent - if parent.name in _APPS_DIRS and parent.parent.name == ETC_LEAF: + if parent.name in _APPS_DIRS and _is_splunk_etc_dir(parent.parent): return candidate.name return None +def _get_running_app_name() -> Optional[str]: + """Return the name of the Splunk app whose script is running. + + Tries the main script first (modular inputs, alert actions and search + commands run from ``etc/apps//bin``; this path also keeps + symlinked app directories intact), then falls back to the app that + bundles solnlib. + + Returns: + App name, or None if neither path is inside a Splunk apps directory. + """ + main_path = getattr(__main__, "__file__", None) or ( + sys.argv[0] if sys.argv else None + ) + return (main_path and _get_app_name_from_path(main_path)) or ( + _get_app_name_from_path(__file__) + ) + + def get_splunk_host_info(session_key: Optional[str] = None) -> tuple: """Get splunk host info. diff --git a/tests/unit/test_observability.py b/tests/unit/test_observability.py index 86fefc30..456c0553 100644 --- a/tests/unit/test_observability.py +++ b/tests/unit/test_observability.py @@ -766,7 +766,7 @@ def test_register_instrument_calls_callback(self, logger, monkeypatch): def test_read_ta_info_returns_values(self, logger, monkeypatch): # Arrange monkeypatch.setattr( - "solnlib.observability._get_app_name_from_path", lambda: "my_ta" + "solnlib.observability._get_running_app_name", lambda: "my_ta" ) monkeypatch.setattr( "solnlib.observability.get_conf_stanzas", @@ -787,7 +787,7 @@ def test_read_ta_info_returns_values(self, logger, monkeypatch): def test_read_ta_info_always_scoped_to_owning_app(self, logger, monkeypatch): # Arrange monkeypatch.setattr( - "solnlib.observability._get_app_name_from_path", + "solnlib.observability._get_running_app_name", lambda: "Splunk_TA_google-cloudplatform", ) get_conf = MagicMock( @@ -830,7 +830,7 @@ def fake_get_conf_stanzas(conf, app_name=None): "solnlib.observability.get_conf_stanzas", fake_get_conf_stanzas ) monkeypatch.setattr( - "solnlib.observability._get_app_name_from_path", + "solnlib.observability._get_running_app_name", lambda: "Splunk_TA_google-cloudplatform", ) svc = _make_service(logger, monkeypatch) @@ -840,7 +840,7 @@ def fake_get_conf_stanzas(conf, app_name=None): def test_read_ta_info_missing_id_falls_back_to_app_dir(self, logger, monkeypatch): # Arrange monkeypatch.setattr( - "solnlib.observability._get_app_name_from_path", lambda: "Splunk_TA_aws" + "solnlib.observability._get_running_app_name", lambda: "Splunk_TA_aws" ) monkeypatch.setattr( "solnlib.observability.get_conf_stanzas", @@ -852,9 +852,7 @@ def test_read_ta_info_missing_id_falls_back_to_app_dir(self, logger, monkeypatch def test_read_ta_info_no_detectable_app_returns_none(self, logger, monkeypatch): # Arrange - monkeypatch.setattr( - "solnlib.observability._get_app_name_from_path", lambda: None - ) + monkeypatch.setattr("solnlib.observability._get_running_app_name", lambda: None) get_conf = MagicMock() monkeypatch.setattr("solnlib.observability.get_conf_stanzas", get_conf) svc = _make_service(logger, monkeypatch) @@ -869,9 +867,7 @@ def test_read_ta_info_no_detectable_app_returns_none(self, logger, monkeypatch): def test_no_detectable_app_skips_telemetry(self, logger, monkeypatch): # Arrange - monkeypatch.setattr( - "solnlib.observability._get_app_name_from_path", lambda: None - ) + monkeypatch.setattr("solnlib.observability._get_running_app_name", lambda: None) monkeypatch.setattr( "solnlib.observability.ObservabilityService._create_otlp_exporter", lambda self: None, @@ -885,7 +881,7 @@ def test_no_detectable_app_skips_telemetry(self, logger, monkeypatch): def test_read_ta_info_handles_exception(self, logger, monkeypatch): # Arrange monkeypatch.setattr( - "solnlib.observability._get_app_name_from_path", lambda: "my_ta" + "solnlib.observability._get_running_app_name", lambda: "my_ta" ) monkeypatch.setattr( "solnlib.observability.ObservabilityService._create_otlp_exporter", @@ -1621,7 +1617,7 @@ def test_read_ta_info_exception_uses_safe_str(self, logger, monkeypatch): "solnlib.observability._safe_exception_str", lambda error: "SAFE" ) monkeypatch.setattr( - "solnlib.observability._get_app_name_from_path", lambda: "my_ta" + "solnlib.observability._get_running_app_name", lambda: "my_ta" ) monkeypatch.setattr( "solnlib.observability.ObservabilityService._create_otlp_exporter", diff --git a/tests/unit/test_splunkenv.py b/tests/unit/test_splunkenv.py index 11905035..f8f74166 100644 --- a/tests/unit/test_splunkenv.py +++ b/tests/unit/test_splunkenv.py @@ -179,10 +179,66 @@ def test_splunkenv_used_outside_splunk_env(): ("checkout/solnlib/splunkenv.py", None), ], ) -def test_get_app_name_from_path(tmp_path, rel_path, expected): +def test_get_app_name_from_path(tmp_path, monkeypatch, rel_path, expected): + monkeypatch.delenv("SPLUNK_ETC", raising=False) assert splunkenv._get_app_name_from_path(str(tmp_path / rel_path)) == expected -def test_get_app_name_from_path_defaults_to_module_location(): - # The repo checkout is not under etc/apps. - assert splunkenv._get_app_name_from_path() is None +def test_get_app_name_from_path_relocated_splunk_etc(tmp_path, monkeypatch): + splunk_etc = tmp_path / "splunkcfg" + script = splunk_etc / "apps" / "Splunk_TA_aws" / "bin" / "input.py" + monkeypatch.setenv("SPLUNK_ETC", str(splunk_etc)) + assert splunkenv._get_app_name_from_path(str(script)) == "Splunk_TA_aws" + monkeypatch.delenv("SPLUNK_ETC") + assert splunkenv._get_app_name_from_path(str(script)) is None + + +def test_get_app_name_from_path_keeps_symlinked_app_name(tmp_path): + target = tmp_path / "src" / "MyTA" + (target / "bin").mkdir(parents=True) + (tmp_path / "etc" / "apps").mkdir(parents=True) + (tmp_path / "etc" / "apps" / "MyTA").symlink_to(target) + script = tmp_path / "etc" / "apps" / "MyTA" / "bin" / "input.py" + assert splunkenv._get_app_name_from_path(str(script)) == "MyTA" + + +def test_get_running_app_name_prefers_main_script(tmp_path, monkeypatch): + monkeypatch.setattr( + splunkenv.__main__, + "__file__", + str(tmp_path / "etc/apps/TA_running/bin/input.py"), + raising=False, + ) + monkeypatch.setattr( + splunkenv, "__file__", str(tmp_path / "etc/apps/TA_lib/lib/solnlib/x.py") + ) + assert splunkenv._get_running_app_name() == "TA_running" + + +def test_get_running_app_name_uses_argv_without_main_file(tmp_path, monkeypatch): + monkeypatch.delattr(splunkenv.__main__, "__file__", raising=False) + monkeypatch.setattr( + splunkenv.sys, "argv", [str(tmp_path / "etc/apps/TA_argv/bin/input.py")] + ) + assert splunkenv._get_running_app_name() == "TA_argv" + + +def test_get_running_app_name_falls_back_to_solnlib_path(tmp_path, monkeypatch): + monkeypatch.setattr( + splunkenv.__main__, + "__file__", + str(tmp_path / "opt/splunk/lib/python3/splunk/persistconn/appserver.py"), + raising=False, + ) + monkeypatch.setattr( + splunkenv, "__file__", str(tmp_path / "etc/apps/TA_lib/lib/solnlib/x.py") + ) + assert splunkenv._get_running_app_name() == "TA_lib" + + +def test_get_running_app_name_none_outside_splunk(tmp_path, monkeypatch): + monkeypatch.setattr( + splunkenv.__main__, "__file__", str(tmp_path / "x.py"), raising=False + ) + monkeypatch.setattr(splunkenv, "__file__", str(tmp_path / "solnlib/x.py")) + assert splunkenv._get_running_app_name() is None From 06e9254715f6515d1d610adc311af142487f69db Mon Sep 17 00:00:00 2001 From: Sudesh0119 Date: Wed, 28 Oct 2026 11:35:54 +0530 Subject: [PATCH 3/5] fix: harden running add-on detection Prefer the outermost etc/apps match, compare paths case-insensitively on Windows, tolerate a missing sys.argv or path errors, and keep detection failures inside _read_ta_info so the solnlib-path fallback still applies. Co-Authored-By: Claude Code --- solnlib/observability.py | 18 +++++++++--------- solnlib/splunkenv.py | 32 +++++++++++++++++++++----------- tests/unit/test_observability.py | 11 +++++++++++ tests/unit/test_splunkenv.py | 20 ++++++++++++++++++++ 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/solnlib/observability.py b/solnlib/observability.py index 5330b883..ffa70a39 100644 --- a/solnlib/observability.py +++ b/solnlib/observability.py @@ -435,8 +435,8 @@ def __init__( if not ta_name: raise ValueError( - "ta_name could not be determined: pass it explicitly or ensure " - "app.conf is readable via btool" + "ta_name could not be determined: pass it explicitly or run " + "the add-on from etc/apps/" ) resource = Resource( @@ -518,14 +518,14 @@ def _read_ta_info(self) -> tuple[Optional[str], Optional[str]]: owning app cannot be detected or ``app.conf`` cannot be read; the version alone is ``None`` when ``[launcher] version`` is missing. """ - app_name = _get_running_app_name() - if not app_name: - self._logger.info( - "Could not determine the running add-on from the script or " - "solnlib path; pass ta_name explicitly to enable telemetry" - ) - return None, None try: + app_name = _get_running_app_name() + if not app_name: + self._logger.info( + "Could not determine the running add-on from the script " + "or solnlib path; app.conf not read" + ) + return None, None stanzas = get_conf_stanzas("app", app_name=app_name) ta_name = stanzas.get("id", {}).get("name") or app_name ta_version = stanzas.get("launcher", {}).get("version") or None diff --git a/solnlib/splunkenv.py b/solnlib/splunkenv.py index 3b12d8dd..3846ba33 100644 --- a/solnlib/splunkenv.py +++ b/solnlib/splunkenv.py @@ -96,10 +96,12 @@ def make_splunkhome_path(parts: Union[list, tuple]) -> str: def _is_splunk_etc_dir(path: Path) -> bool: - if path.name == ETC_LEAF: + if op.normcase(path.name) == ETC_LEAF: return True splunk_etc = os.environ.get("SPLUNK_ETC") - return bool(splunk_etc) and op.realpath(splunk_etc) == op.realpath(path) + return bool(splunk_etc) and op.normcase(op.realpath(splunk_etc)) == op.normcase( + op.realpath(path) + ) def _get_app_name_from_path(path: str) -> Optional[str]: @@ -116,12 +118,21 @@ def _get_app_name_from_path(path: str) -> Optional[str]: Returns: App name, or None if `path` is not inside a Splunk apps directory. """ - # abspath, not resolve(): apps symlinked into etc/apps must keep their name. - absolute = Path(op.abspath(path)) - for candidate in (absolute, *absolute.parents): - parent = candidate.parent - if parent.name in _APPS_DIRS and _is_splunk_etc_dir(parent.parent): - return candidate.name + # Not utils.get_appname_from_path: that one returns "-", ignores + # SPLUNK_ETC and slave-apps, and is public, so its behaviour is kept. + try: + # abspath, not resolve(): apps symlinked into etc/apps keep their name. + absolute = Path(op.abspath(path)) + # Outermost match first: Splunk's etc/apps sits above anything an app + # vendors that happens to contain etc/apps. + for candidate in reversed((absolute, *absolute.parents)): + parent = candidate.parent + if op.normcase(parent.name) in _APPS_DIRS and _is_splunk_etc_dir( + parent.parent + ): + return candidate.name + except (OSError, ValueError): + pass return None @@ -136,9 +147,8 @@ def _get_running_app_name() -> Optional[str]: Returns: App name, or None if neither path is inside a Splunk apps directory. """ - main_path = getattr(__main__, "__file__", None) or ( - sys.argv[0] if sys.argv else None - ) + argv = getattr(sys, "argv", None) + main_path = getattr(__main__, "__file__", None) or (argv[0] if argv else None) return (main_path and _get_app_name_from_path(main_path)) or ( _get_app_name_from_path(__file__) ) diff --git a/tests/unit/test_observability.py b/tests/unit/test_observability.py index 456c0553..bde40a54 100644 --- a/tests/unit/test_observability.py +++ b/tests/unit/test_observability.py @@ -878,6 +878,17 @@ def test_no_detectable_app_skips_telemetry(self, logger, monkeypatch): assert svc._meter is None logger.warning.assert_not_called() + def test_read_ta_info_app_detection_error_returns_none(self, logger, monkeypatch): + monkeypatch.setattr( + "solnlib.observability._get_running_app_name", + MagicMock(side_effect=RuntimeError("boom")), + ) + svc = _make_service(logger, monkeypatch) + logger.reset_mock() + assert svc._read_ta_info() == (None, None) + logger.info.assert_called() + logger.warning.assert_not_called() + def test_read_ta_info_handles_exception(self, logger, monkeypatch): # Arrange monkeypatch.setattr( diff --git a/tests/unit/test_splunkenv.py b/tests/unit/test_splunkenv.py index f8f74166..cc185f40 100644 --- a/tests/unit/test_splunkenv.py +++ b/tests/unit/test_splunkenv.py @@ -176,6 +176,9 @@ def test_splunkenv_used_outside_splunk_env(): ("etc/apps/Splunk_TA_aws", "Splunk_TA_aws"), ("etc/apps", None), ("src/apps/Splunk_TA_aws/lib/solnlib/splunkenv.py", None), + ("etc/apps/Splunk_TA_aws/lib/pkg/etc/apps/inner/x.py", "Splunk_TA_aws"), + ("etc/deployment-apps/Splunk_TA_aws/bin/input.py", None), + ("etc/shcluster/apps/Splunk_TA_aws/bin/input.py", None), ("checkout/solnlib/splunkenv.py", None), ], ) @@ -223,6 +226,23 @@ def test_get_running_app_name_uses_argv_without_main_file(tmp_path, monkeypatch) assert splunkenv._get_running_app_name() == "TA_argv" +def test_get_running_app_name_without_sys_argv(tmp_path, monkeypatch): + monkeypatch.delattr(splunkenv.__main__, "__file__", raising=False) + monkeypatch.delattr(splunkenv.sys, "argv") + monkeypatch.setattr( + splunkenv, "__file__", str(tmp_path / "etc/apps/TA_lib/lib/solnlib/x.py") + ) + assert splunkenv._get_running_app_name() == "TA_lib" + + +def test_get_running_app_name_survives_path_errors(tmp_path, monkeypatch): + monkeypatch.setattr( + splunkenv.__main__, "__file__", "relative/input.py", raising=False + ) + monkeypatch.setattr(splunkenv.op, "abspath", mock.Mock(side_effect=OSError)) + assert splunkenv._get_running_app_name() is None + + def test_get_running_app_name_falls_back_to_solnlib_path(tmp_path, monkeypatch): monkeypatch.setattr( splunkenv.__main__, From 8ba3c80f5baebbe72f6ac590d1797d91c751228c Mon Sep 17 00:00:00 2001 From: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> Date: Mon, 2 Nov 2026 10:17:35 +0000 Subject: [PATCH 4/5] chore(release): 8.2.1-beta.1 ## [8.2.1-beta.1](https://github.com/splunk/addonfactory-solutions-library-python/compare/v8.2.0...v8.2.1-beta.1) (2026-11-02) ### Bug Fixes * ADDON-91029 scope observability app.conf lookup to the owning add-on ([0d589ed](https://github.com/splunk/addonfactory-solutions-library-python/commit/0d589ed673351fe83a4d49be74e4b01c9f746797)) * ADDON-91029 scope observability app.conf lookup to the owning add-on ([#470](https://github.com/splunk/addonfactory-solutions-library-python/issues/470)) ([5d73194](https://github.com/splunk/addonfactory-solutions-library-python/commit/5d731940594ae6ce90b05f2d6e33fd3709092623)), closes [#1027](https://github.com/splunk/addonfactory-solutions-library-python/issues/1027) * detect the running add-on from the main script path ([09f02cd](https://github.com/splunk/addonfactory-solutions-library-python/commit/09f02cd26caf9999542730be6c23c0c0b12fb608)) * harden running add-on detection ([06e9254](https://github.com/splunk/addonfactory-solutions-library-python/commit/06e9254715f6515d1d610adc311af142487f69db)) --- pyproject.toml | 2 +- solnlib/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 09b5f626..d2f6a457 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ [tool.poetry] name = "solnlib" -version = "8.2.0" +version = "8.2.1-beta.1" description = "The Splunk Software Development Kit for Splunk Solutions" authors = ["Splunk "] license = "Apache-2.0" diff --git a/solnlib/__init__.py b/solnlib/__init__.py index ae213d32..4214b8fb 100644 --- a/solnlib/__init__.py +++ b/solnlib/__init__.py @@ -55,4 +55,4 @@ "utils", ] -__version__ = "8.2.0" +__version__ = "8.2.1-beta.1" From db5e4c2dd69c1150c6d10f640e85e13363e76569 Mon Sep 17 00:00:00 2001 From: Sudesh0119 Date: Tue, 3 Nov 2026 17:08:07 +0530 Subject: [PATCH 5/5] fix: fixed the fossa vulnerability fix --- poetry.lock | 153 ++++++++++++++++++++++++++++++++++++++++++++++--- pyproject.toml | 15 ++++- 2 files changed, 157 insertions(+), 11 deletions(-) diff --git a/poetry.lock b/poetry.lock index 26e88bdc..cd118c3c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. [[package]] name = "babel" @@ -396,11 +396,11 @@ description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" groups = ["main", "dev"] +markers = "python_version == \"3.9\"" files = [ {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, ] -markers = {dev = "python_version == \"3.9\""} [package.dependencies] zipp = ">=3.20" @@ -750,6 +750,7 @@ description = "OpenTelemetry Python API" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version == \"3.9\"" files = [ {file = "opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950"}, {file = "opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c"}, @@ -759,6 +760,41 @@ files = [ importlib-metadata = ">=6.0,<8.8.0" typing-extensions = ">=4.5.0" +[[package]] +name = "opentelemetry-api" +version = "1.45.2" +description = "OpenTelemetry Python API" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "opentelemetry_api-1.45.2-py3-none-any.whl", hash = "sha256:2cbd737377e710ee740d261c94e93fa0f08e6d037c18f17513cc0a2d94479a34"}, + {file = "opentelemetry_api-1.45.2.tar.gz", hash = "sha256:d7b0008e21e5fd61cbe7a7f34ca3b6905a78ab677d9bc395e299359c75cda6b4"}, +] + +[package.dependencies] +typing-extensions = ">=4.5.0" + +[[package]] +name = "opentelemetry-exporter-otlp-common" +version = "0.66b2" +description = "OpenTelemetry OTLP HTTP export utilities" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "opentelemetry_exporter_otlp_common-0.66b2-py3-none-any.whl", hash = "sha256:ace86efb75489f925f1ef809856ae5f0c7db534e4afdc624530222228b1236a5"}, + {file = "opentelemetry_exporter_otlp_common-0.66b2.tar.gz", hash = "sha256:06f5a9b88638e310efa1f1d206cdf6b75393e112aba4e174a963416192bd1028"}, +] + +[package.dependencies] +opentelemetry-sdk = ">=1.45.2,<1.46.0" + +[package.extras] +http = ["opentelemetry-exporter-http-transport (==0.66b2)"] + [[package]] name = "opentelemetry-exporter-otlp-proto-common" version = "1.39.1" @@ -766,6 +802,7 @@ description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version == \"3.9\"" files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde"}, {file = "opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464"}, @@ -774,6 +811,22 @@ files = [ [package.dependencies] opentelemetry-proto = "1.39.1" +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.45.2" +description = "OpenTelemetry Protobuf encoding" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "opentelemetry_exporter_otlp_proto_common-1.45.2-py3-none-any.whl", hash = "sha256:af483b24217028892f017096731b794b196b785025d2e6808874720fd0b05a80"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.45.2.tar.gz", hash = "sha256:f1f2806f3f28ce7938033823e50934fcd91595a39de38e69a28a51e19f40fc6d"}, +] + +[package.dependencies] +opentelemetry-proto = "1.45.2" + [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" version = "1.39.1" @@ -781,6 +834,7 @@ description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version == \"3.9\"" files = [ {file = "opentelemetry_exporter_otlp_proto_grpc-1.39.1-py3-none-any.whl", hash = "sha256:fa1c136a05c7e9b4c09f739469cbdb927ea20b34088ab1d959a849b5cc589c18"}, {file = "opentelemetry_exporter_otlp_proto_grpc-1.39.1.tar.gz", hash = "sha256:772eb1c9287485d625e4dbe9c879898e5253fea111d9181140f51291b5fec3ad"}, @@ -788,10 +842,7 @@ files = [ [package.dependencies] googleapis-common-protos = ">=1.57,<2.0" -grpcio = [ - {version = ">=1.63.2,<2.0.0", markers = "python_version < \"3.13\""}, - {version = ">=1.66.2,<2.0.0", markers = "python_version >= \"3.13\""}, -] +grpcio = {version = ">=1.63.2,<2.0.0", markers = "python_version < \"3.13\""} opentelemetry-api = ">=1.15,<2.0" opentelemetry-exporter-otlp-proto-common = "1.39.1" opentelemetry-proto = "1.39.1" @@ -801,6 +852,35 @@ typing-extensions = ">=4.6.0" [package.extras] gcp-auth = ["opentelemetry-exporter-credential-provider-gcp (>=0.59b0)"] +[[package]] +name = "opentelemetry-exporter-otlp-proto-grpc" +version = "1.45.2" +description = "OpenTelemetry Collector Protobuf over gRPC Exporter" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "opentelemetry_exporter_otlp_proto_grpc-1.45.2-py3-none-any.whl", hash = "sha256:8a48d60835e90536846bd82de487bfbd81bccb5a1b3d0fb82edeea17db07395c"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.45.2.tar.gz", hash = "sha256:9e4a6490ca7711479fec7c519425bc09808abbc9a2a1775528edecf200d19a5a"}, +] + +[package.dependencies] +googleapis-common-protos = ">=1.57,<2.0" +grpcio = [ + {version = ">=1.63.2,<2.0.0", markers = "python_version < \"3.13\""}, + {version = ">=1.66.2,<2.0.0", markers = "python_version == \"3.13\""}, +] +opentelemetry-api = ">=1.15,<2.0" +opentelemetry-exporter-otlp-common = "0.66b2" +opentelemetry-exporter-otlp-proto-common = "1.45.2" +opentelemetry-proto = "1.45.2" +opentelemetry-sdk = ">=1.45.2,<1.46.0" +typing-extensions = ">=4.6.0" + +[package.extras] +gcp-auth = ["opentelemetry-exporter-credential-provider-gcp (>=0.59b0)"] + [[package]] name = "opentelemetry-proto" version = "1.39.1" @@ -808,6 +888,7 @@ description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version == \"3.9\"" files = [ {file = "opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007"}, {file = "opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8"}, @@ -816,6 +897,22 @@ files = [ [package.dependencies] protobuf = ">=5.0,<7.0" +[[package]] +name = "opentelemetry-proto" +version = "1.45.2" +description = "OpenTelemetry Python Proto" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "opentelemetry_proto-1.45.2-py3-none-any.whl", hash = "sha256:03a7ca1b43f89fd5b965d5975902bb2515c0c86e2428b456836e9a6149c6235a"}, + {file = "opentelemetry_proto-1.45.2.tar.gz", hash = "sha256:bd8d23cf770605b0af9b7be2071d26a464fea289d35429abfd2a66226114ad64"}, +] + +[package.dependencies] +protobuf = ">=5.0,<8.0" + [[package]] name = "opentelemetry-sdk" version = "1.39.1" @@ -823,6 +920,7 @@ description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version == \"3.9\"" files = [ {file = "opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c"}, {file = "opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6"}, @@ -833,6 +931,27 @@ opentelemetry-api = "1.39.1" opentelemetry-semantic-conventions = "0.60b1" typing-extensions = ">=4.5.0" +[[package]] +name = "opentelemetry-sdk" +version = "1.45.2" +description = "OpenTelemetry Python SDK" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "opentelemetry_sdk-1.45.2-py3-none-any.whl", hash = "sha256:ff974ea70ee4f1d51e2ec152db37c2b626c7bf82bb3f7d86114fd8704e7199c6"}, + {file = "opentelemetry_sdk-1.45.2.tar.gz", hash = "sha256:7eec4de13dd21ff3a1670dcb0e02079e692736f94d733dfdce5af461d884248d"}, +] + +[package.dependencies] +opentelemetry-api = "1.45.2" +opentelemetry-semantic-conventions = "0.66b2" +typing-extensions = ">=4.5.0" + +[package.extras] +file-configuration = ["opentelemetry-configuration (==0.66b2)"] + [[package]] name = "opentelemetry-semantic-conventions" version = "0.60b1" @@ -840,6 +959,7 @@ description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.9" groups = ["main"] +markers = "python_version == \"3.9\"" files = [ {file = "opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb"}, {file = "opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953"}, @@ -849,6 +969,23 @@ files = [ opentelemetry-api = "1.39.1" typing-extensions = ">=4.5.0" +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.66b2" +description = "OpenTelemetry Semantic Conventions" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.10\"" +files = [ + {file = "opentelemetry_semantic_conventions-0.66b2-py3-none-any.whl", hash = "sha256:4cbe61e5f9275d57ab31708446e7938ef4291ebbe6a7fca6f7a629c5acae87d8"}, + {file = "opentelemetry_semantic_conventions-0.66b2.tar.gz", hash = "sha256:7da5f1a5dc4383eb66711d4d3d3e71824d9403ac1559fea021cc103c1a4c4976"}, +] + +[package.dependencies] +opentelemetry-api = "1.45.2" +typing-extensions = ">=4.5.0" + [[package]] name = "packaging" version = "26.0" @@ -1318,11 +1455,11 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" groups = ["main", "dev"] +markers = "python_version == \"3.9\"" files = [ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] -markers = {dev = "python_version == \"3.9\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] @@ -1335,4 +1472,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<3.14" -content-hash = "5726ed25f2a5dea513ef677a4cf5655cc940a6d34c0b62238f8b65ae17d652cb" +content-hash = "34b6dafc29caf1e657011aa4c271879195df663bc4898ba18842d51005f46a68" diff --git a/pyproject.toml b/pyproject.toml index d2f6a457..c68ffbf1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,9 +41,18 @@ python = ">=3.9,<3.14" sortedcontainers = ">=2" defusedxml = ">=0.7" splunk-sdk = ">=2.0.2" -opentelemetry-api = ">=1.39.1,<2" -opentelemetry-sdk = ">=1.39.1,<2" -opentelemetry-exporter-otlp-proto-grpc = ">=1.39.1,<2" +opentelemetry-api = [ + {version = ">=1.45.1,<2", python = ">=3.10"}, + {version = ">=1.39.1,<2", python = "<3.10"}, +] +opentelemetry-sdk = [ + {version = ">=1.45.1,<2", python = ">=3.10"}, + {version = ">=1.39.1,<2", python = "<3.10"}, +] +opentelemetry-exporter-otlp-proto-grpc = [ + {version = ">=1.45.1,<2", python = ">=3.10"}, + {version = ">=1.39.1,<2", python = "<3.10"}, +] grpcio = ">=1.74.0" [tool.poetry.group.dev.dependencies]