From bebd7a4d093d61f14d94888066afc7135ff367c7 Mon Sep 17 00:00:00 2001 From: Kyle-Neale Date: Fri, 8 May 2026 12:53:55 -0400 Subject: [PATCH 1/5] feat(base): add get_agent_embedded_path helper Resolve paths under the agent's `embedded` directory by reading `run_path` from the agent config instead of assuming the install sits at `/opt/datadog-agent`. Works for both standard installs and Remote-Management installs at `/opt/datadog-packages/datadog-agent/`. Returns `None` when `run_path` is unset so callers can decide whether the miss is fatal (raise) or skip a fallback. Refs: AI-5681 Follow-up to #20574 which inlined this lookup in glusterfs. Co-Authored-By: Claude Opus 4.7 (1M context) --- datadog_checks_base/changelog.d/23487.fixed | 1 + .../datadog_checks/base/utils/agent/common.py | 22 +++++++++ .../tests/base/utils/test_agent_common.py | 49 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 datadog_checks_base/changelog.d/23487.fixed create mode 100644 datadog_checks_base/tests/base/utils/test_agent_common.py diff --git a/datadog_checks_base/changelog.d/23487.fixed b/datadog_checks_base/changelog.d/23487.fixed new file mode 100644 index 0000000000000..c420621b3d3a1 --- /dev/null +++ b/datadog_checks_base/changelog.d/23487.fixed @@ -0,0 +1 @@ +Add `get_agent_embedded_path` helper to resolve paths under the agent's embedded directory regardless of install location. \ No newline at end of file diff --git a/datadog_checks_base/datadog_checks/base/utils/agent/common.py b/datadog_checks_base/datadog_checks/base/utils/agent/common.py index 18a15fb62727d..39912ae810b93 100644 --- a/datadog_checks_base/datadog_checks/base/utils/agent/common.py +++ b/datadog_checks_base/datadog_checks/base/utils/agent/common.py @@ -1,5 +1,27 @@ # (C) Datadog, Inc. 2019-present # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) +import os + +try: + import datadog_agent +except ImportError: + from datadog_checks.base.stubs import datadog_agent + METRIC_NAMESPACE_METRICS = 'datadog.agent.metrics' METRIC_NAMESPACE_PROFILE = 'datadog.agent.profile' + + +def get_agent_embedded_path(*parts: str) -> str | None: + """Resolve a path under the agent's `embedded` directory from the agent's `run_path` config. + + Returns ``None`` when ``run_path`` is unset so callers can decide whether the + miss is fatal or merely skips a fallback. Works for both the standard install + (``/opt/datadog-agent/run``) and Remote-Management installs + (``/opt/datadog-packages/datadog-agent//run``). + """ + run_path = datadog_agent.get_config('run_path') + if not run_path: + return None + install_path = run_path[:-4] if run_path.endswith('/run') else run_path + return os.path.join(install_path, 'embedded', *parts) diff --git a/datadog_checks_base/tests/base/utils/test_agent_common.py b/datadog_checks_base/tests/base/utils/test_agent_common.py new file mode 100644 index 0000000000000..09ce0c9f21c95 --- /dev/null +++ b/datadog_checks_base/tests/base/utils/test_agent_common.py @@ -0,0 +1,49 @@ +# (C) Datadog, Inc. 2026-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +import mock + +from datadog_checks.base.utils.agent.common import get_agent_embedded_path + + +class TestGetAgentEmbeddedPath: + def test_standard_install(self): + with mock.patch( + 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', + return_value='/opt/datadog-agent/run', + ): + assert get_agent_embedded_path('sbin', 'gstatus') == '/opt/datadog-agent/embedded/sbin/gstatus' + + def test_remote_management_install(self): + with mock.patch( + 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', + return_value='/opt/datadog-packages/datadog-agent/7.79.0/run', + ): + assert ( + get_agent_embedded_path('sbin', 'gstatus') + == '/opt/datadog-packages/datadog-agent/7.79.0/embedded/sbin/gstatus' + ) + + def test_missing_run_path_returns_none(self): + with mock.patch( + 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', + return_value='', + ): + assert get_agent_embedded_path('sbin', 'gstatus') is None + + def test_run_path_without_trailing_run(self): + with mock.patch( + 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', + return_value='/custom/agent/dir', + ): + assert ( + get_agent_embedded_path('ssl', 'certs', 'cacert.pem') + == '/custom/agent/dir/embedded/ssl/certs/cacert.pem' + ) + + def test_no_parts_returns_embedded_dir(self): + with mock.patch( + 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', + return_value='/opt/datadog-agent/run', + ): + assert get_agent_embedded_path() == '/opt/datadog-agent/embedded' From 9aaabaa3eed3497c88b70a5cb1bd535abe4de36e Mon Sep 17 00:00:00 2001 From: Kyle-Neale Date: Mon, 11 May 2026 16:20:02 -0400 Subject: [PATCH 2/5] fix(base): rename changelog fragment to PR number; parametrize helper tests Co-Authored-By: Claude Opus 4.7 (1M context) --- .../changelog.d/{23487.fixed => 23644.fixed} | 0 .../tests/base/utils/test_agent_common.py | 73 ++++++++----------- 2 files changed, 32 insertions(+), 41 deletions(-) rename datadog_checks_base/changelog.d/{23487.fixed => 23644.fixed} (100%) diff --git a/datadog_checks_base/changelog.d/23487.fixed b/datadog_checks_base/changelog.d/23644.fixed similarity index 100% rename from datadog_checks_base/changelog.d/23487.fixed rename to datadog_checks_base/changelog.d/23644.fixed diff --git a/datadog_checks_base/tests/base/utils/test_agent_common.py b/datadog_checks_base/tests/base/utils/test_agent_common.py index 09ce0c9f21c95..0fb2358f7dd72 100644 --- a/datadog_checks_base/tests/base/utils/test_agent_common.py +++ b/datadog_checks_base/tests/base/utils/test_agent_common.py @@ -2,48 +2,39 @@ # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) import mock +import pytest from datadog_checks.base.utils.agent.common import get_agent_embedded_path -class TestGetAgentEmbeddedPath: - def test_standard_install(self): - with mock.patch( - 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', - return_value='/opt/datadog-agent/run', - ): - assert get_agent_embedded_path('sbin', 'gstatus') == '/opt/datadog-agent/embedded/sbin/gstatus' - - def test_remote_management_install(self): - with mock.patch( - 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', - return_value='/opt/datadog-packages/datadog-agent/7.79.0/run', - ): - assert ( - get_agent_embedded_path('sbin', 'gstatus') - == '/opt/datadog-packages/datadog-agent/7.79.0/embedded/sbin/gstatus' - ) - - def test_missing_run_path_returns_none(self): - with mock.patch( - 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', - return_value='', - ): - assert get_agent_embedded_path('sbin', 'gstatus') is None - - def test_run_path_without_trailing_run(self): - with mock.patch( - 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', - return_value='/custom/agent/dir', - ): - assert ( - get_agent_embedded_path('ssl', 'certs', 'cacert.pem') - == '/custom/agent/dir/embedded/ssl/certs/cacert.pem' - ) - - def test_no_parts_returns_embedded_dir(self): - with mock.patch( - 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', - return_value='/opt/datadog-agent/run', - ): - assert get_agent_embedded_path() == '/opt/datadog-agent/embedded' +@pytest.mark.parametrize( + 'run_path, parts, expected', + [ + pytest.param( + '/opt/datadog-agent/run', + ('sbin', 'gstatus'), + '/opt/datadog-agent/embedded/sbin/gstatus', + id='standard_install', + ), + pytest.param( + '/opt/datadog-packages/datadog-agent/7.79.0/run', + ('sbin', 'gstatus'), + '/opt/datadog-packages/datadog-agent/7.79.0/embedded/sbin/gstatus', + id='remote_management_install', + ), + pytest.param( + '/custom/agent/dir', + ('ssl', 'certs', 'cacert.pem'), + '/custom/agent/dir/embedded/ssl/certs/cacert.pem', + id='run_path_without_trailing_run', + ), + pytest.param('/opt/datadog-agent/run', (), '/opt/datadog-agent/embedded', id='no_parts'), + pytest.param('', ('sbin', 'gstatus'), None, id='missing_run_path'), + ], +) +def test_get_agent_embedded_path(run_path, parts, expected): + with mock.patch( + 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', + return_value=run_path, + ): + assert get_agent_embedded_path(*parts) == expected From acd83145c7b1933e48ed3b88ab5e17c26a4bc274 Mon Sep 17 00:00:00 2001 From: Kyle-Neale Date: Mon, 11 May 2026 17:58:02 -0400 Subject: [PATCH 3/5] fix(base): make get_agent_embedded_path tests platform-aware Use os.path.join when building expected values so backslash path separators on Windows runners no longer break assertions. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../tests/base/utils/test_agent_common.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/datadog_checks_base/tests/base/utils/test_agent_common.py b/datadog_checks_base/tests/base/utils/test_agent_common.py index 0fb2358f7dd72..207cf49cdd05f 100644 --- a/datadog_checks_base/tests/base/utils/test_agent_common.py +++ b/datadog_checks_base/tests/base/utils/test_agent_common.py @@ -1,6 +1,8 @@ # (C) Datadog, Inc. 2026-present # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) +import os + import mock import pytest @@ -8,33 +10,32 @@ @pytest.mark.parametrize( - 'run_path, parts, expected', + 'run_path, parts, expected_install', [ - pytest.param( - '/opt/datadog-agent/run', - ('sbin', 'gstatus'), - '/opt/datadog-agent/embedded/sbin/gstatus', - id='standard_install', - ), + pytest.param('/opt/datadog-agent/run', ('sbin', 'gstatus'), '/opt/datadog-agent', id='standard_install'), pytest.param( '/opt/datadog-packages/datadog-agent/7.79.0/run', ('sbin', 'gstatus'), - '/opt/datadog-packages/datadog-agent/7.79.0/embedded/sbin/gstatus', + '/opt/datadog-packages/datadog-agent/7.79.0', id='remote_management_install', ), pytest.param( - '/custom/agent/dir', - ('ssl', 'certs', 'cacert.pem'), - '/custom/agent/dir/embedded/ssl/certs/cacert.pem', - id='run_path_without_trailing_run', + '/custom/agent/dir', ('ssl', 'certs', 'cacert.pem'), '/custom/agent/dir', id='run_path_without_trailing_run' ), - pytest.param('/opt/datadog-agent/run', (), '/opt/datadog-agent/embedded', id='no_parts'), - pytest.param('', ('sbin', 'gstatus'), None, id='missing_run_path'), + pytest.param('/opt/datadog-agent/run', (), '/opt/datadog-agent', id='no_parts'), ], ) -def test_get_agent_embedded_path(run_path, parts, expected): +def test_get_agent_embedded_path(run_path, parts, expected_install): with mock.patch( 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', return_value=run_path, ): - assert get_agent_embedded_path(*parts) == expected + assert get_agent_embedded_path(*parts) == os.path.join(expected_install, 'embedded', *parts) + + +def test_get_agent_embedded_path_missing_run_path_returns_none(): + with mock.patch( + 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', + return_value='', + ): + assert get_agent_embedded_path('sbin', 'gstatus') is None From 603a539e78b6d4c2223d3ae6b77149c1e339a184 Mon Sep 17 00:00:00 2001 From: Kyle-Neale Date: Tue, 12 May 2026 11:28:35 -0400 Subject: [PATCH 4/5] fix(base): handle Windows in get_agent_embedded_path On Windows the agent's `run_path` is `%ProgramData%\Datadog\run`, a writable cache directory unrelated to the install directory, so the prior logic of stripping `/run` from `run_path` would have returned a nonsense path. Derive the install directory from `sys.executable` on Windows (the agent's embedded Python lives under the install dir), and use the `embedded3` directory name, matching the existing FIPS helper. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../datadog_checks/base/utils/agent/common.py | 19 ++++++++--- .../tests/base/utils/test_agent_common.py | 33 +++++++++++++++---- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/datadog_checks_base/datadog_checks/base/utils/agent/common.py b/datadog_checks_base/datadog_checks/base/utils/agent/common.py index 39912ae810b93..a0122c5863f2a 100644 --- a/datadog_checks_base/datadog_checks/base/utils/agent/common.py +++ b/datadog_checks_base/datadog_checks/base/utils/agent/common.py @@ -2,6 +2,7 @@ # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) import os +import sys try: import datadog_agent @@ -13,13 +14,21 @@ def get_agent_embedded_path(*parts: str) -> str | None: - """Resolve a path under the agent's `embedded` directory from the agent's `run_path` config. + """Resolve a path under the agent's `embedded` directory. - Returns ``None`` when ``run_path`` is unset so callers can decide whether the - miss is fatal or merely skips a fallback. Works for both the standard install - (``/opt/datadog-agent/run``) and Remote-Management installs - (``/opt/datadog-packages/datadog-agent//run``). + On Linux, derives the install directory from the agent's ``run_path`` config, + which handles both the standard install (``/opt/datadog-agent/run``) and + Remote-Management installs (``/opt/datadog-packages/datadog-agent//run``). + Returns ``None`` when ``run_path`` is unset. + + On Windows, ``run_path`` is a writable cache directory under ``%ProgramData%`` + that is unrelated to the install directory, so the install directory is derived + from ``sys.executable`` (the agent's embedded Python) instead, and the embedded + directory is named ``embedded3``. """ + if os.name == 'nt': + install_path = sys.executable.split('embedded')[0].rstrip(os.sep) + return os.path.join(install_path, 'embedded3', *parts) run_path = datadog_agent.get_config('run_path') if not run_path: return None diff --git a/datadog_checks_base/tests/base/utils/test_agent_common.py b/datadog_checks_base/tests/base/utils/test_agent_common.py index 207cf49cdd05f..7bdbfcf42f168 100644 --- a/datadog_checks_base/tests/base/utils/test_agent_common.py +++ b/datadog_checks_base/tests/base/utils/test_agent_common.py @@ -25,17 +25,36 @@ pytest.param('/opt/datadog-agent/run', (), '/opt/datadog-agent', id='no_parts'), ], ) -def test_get_agent_embedded_path(run_path, parts, expected_install): - with mock.patch( - 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', - return_value=run_path, +def test_get_agent_embedded_path_posix(run_path, parts, expected_install): + with ( + mock.patch('datadog_checks.base.utils.agent.common.os.name', 'posix'), + mock.patch( + 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', + return_value=run_path, + ), ): assert get_agent_embedded_path(*parts) == os.path.join(expected_install, 'embedded', *parts) def test_get_agent_embedded_path_missing_run_path_returns_none(): - with mock.patch( - 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', - return_value='', + with ( + mock.patch('datadog_checks.base.utils.agent.common.os.name', 'posix'), + mock.patch( + 'datadog_checks.base.utils.agent.common.datadog_agent.get_config', + return_value='', + ), ): assert get_agent_embedded_path('sbin', 'gstatus') is None + + +def test_get_agent_embedded_path_windows_uses_sys_executable(): + """On Windows, derive from sys.executable and use the embedded3 directory.""" + install_dir = r'C:\Program Files\Datadog\Datadog Agent' + sys_executable = os.path.join(install_dir, 'embedded3', 'python.exe') + with ( + mock.patch('datadog_checks.base.utils.agent.common.os.name', 'nt'), + mock.patch('datadog_checks.base.utils.agent.common.sys.executable', sys_executable), + ): + assert get_agent_embedded_path('ssl', 'certs', 'cacert.pem') == os.path.join( + install_dir, 'embedded3', 'ssl', 'certs', 'cacert.pem' + ) From 61c3a29c1e2a3ec93cdfbca775d4d01fbbddd6cd Mon Sep 17 00:00:00 2001 From: Kyle-Neale Date: Tue, 12 May 2026 14:52:59 -0400 Subject: [PATCH 5/5] fix(base): trim get_agent_embedded_path docstring Reduce to a one-liner per AGENTS.md; install-path derivation is clear from the code. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../datadog_checks/base/utils/agent/common.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/datadog_checks_base/datadog_checks/base/utils/agent/common.py b/datadog_checks_base/datadog_checks/base/utils/agent/common.py index a0122c5863f2a..cc6ce6d0c79d3 100644 --- a/datadog_checks_base/datadog_checks/base/utils/agent/common.py +++ b/datadog_checks_base/datadog_checks/base/utils/agent/common.py @@ -14,18 +14,7 @@ def get_agent_embedded_path(*parts: str) -> str | None: - """Resolve a path under the agent's `embedded` directory. - - On Linux, derives the install directory from the agent's ``run_path`` config, - which handles both the standard install (``/opt/datadog-agent/run``) and - Remote-Management installs (``/opt/datadog-packages/datadog-agent//run``). - Returns ``None`` when ``run_path`` is unset. - - On Windows, ``run_path`` is a writable cache directory under ``%ProgramData%`` - that is unrelated to the install directory, so the install directory is derived - from ``sys.executable`` (the agent's embedded Python) instead, and the embedded - directory is named ``embedded3``. - """ + """Resolve a path under the agent's `embedded` directory, or ``None`` if unavailable.""" if os.name == 'nt': install_path = sys.executable.split('embedded')[0].rstrip(os.sep) return os.path.join(install_path, 'embedded3', *parts)