Skip to content

Commit e42f1d8

Browse files
committed
wire container resource detector in declarative config
When `resource.detection_development.detectors[].container` is set in the config file, attempt to use `ContainerResourceDetector` from the `opentelemetry-resource-detector-containerid` contrib package. Unlike the process, host, and service detectors which are implemented in the core SDK, container detection is not available in core. Rather than adding a hard dependency on the contrib package, we use a lazy import: if the package is installed it is used transparently; if absent a warning is logged with an actionable install instruction. This mirrors the approach taken by other SDKs: JS explicitly skips container detection when no implementation is available, and PHP also defers container detection to a contrib package. See: open-telemetry/opentelemetry-configuration#570 Assisted-by: Claude Sonnet 4.6
1 parent 7f51034 commit e42f1d8

2 files changed

Lines changed: 110 additions & 1 deletion

File tree

opentelemetry-sdk/src/opentelemetry/sdk/_configuration/_resource.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,28 @@ def _run_detectors(
149149
is updated in-place; later detectors overwrite earlier ones for the
150150
same key.
151151
"""
152+
if detector_config.container is not None:
153+
# The container detector is not part of the core SDK. It is provided
154+
# by the opentelemetry-resource-detector-containerid contrib package.
155+
# We attempt a lazy import so the core SDK has no hard dependency on
156+
# contrib; if the package is absent we log an actionable warning rather
157+
# than raising an error. Other SDKs (e.g. JS) similarly skip container
158+
# detection when no implementation is available. See also:
159+
# https://github.com/open-telemetry/opentelemetry-configuration/issues/570
160+
try:
161+
from opentelemetry.resource.detector.containerid import ( # type: ignore[import-not-found] # noqa: PLC0415
162+
ContainerResourceDetector,
163+
)
164+
165+
detected_attrs.update(
166+
ContainerResourceDetector().detect().attributes
167+
)
168+
except ImportError:
169+
_logger.warning(
170+
"container resource detector requested but "
171+
"'opentelemetry-resource-detector-containerid' is not "
172+
"installed; install it to enable container detection"
173+
)
152174

153175

154176
def _filter_attributes(

opentelemetry-sdk/tests/_configuration/test_resource.py

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@
1414

1515
import os
1616
import unittest
17-
from unittest.mock import patch
17+
from unittest.mock import MagicMock, patch
1818

1919
from opentelemetry.sdk._configuration._resource import create_resource
2020
from opentelemetry.sdk._configuration.models import (
2121
AttributeNameValue,
2222
AttributeType,
23+
ExperimentalResourceDetection,
24+
ExperimentalResourceDetector,
2325
)
2426
from opentelemetry.sdk._configuration.models import Resource as ResourceConfig
2527
from opentelemetry.sdk.resources import (
28+
CONTAINER_ID,
2629
SERVICE_NAME,
2730
TELEMETRY_SDK_LANGUAGE,
2831
TELEMETRY_SDK_NAME,
@@ -295,3 +298,87 @@ def test_attributes_list_invalid_pair_skipped(self):
295298
self.assertEqual(resource.attributes["foo"], "bar")
296299
self.assertNotIn("no-equals", resource.attributes)
297300
self.assertTrue(any("no-equals" in msg for msg in cm.output))
301+
302+
303+
class TestContainerResourceDetector(unittest.TestCase):
304+
def _config_with_container(self) -> ResourceConfig:
305+
return ResourceConfig(
306+
detection_development=ExperimentalResourceDetection(
307+
detectors=[ExperimentalResourceDetector(container={})]
308+
)
309+
)
310+
311+
def test_container_detector_not_run_when_absent(self):
312+
resource = create_resource(ResourceConfig())
313+
self.assertNotIn(CONTAINER_ID, resource.attributes)
314+
315+
def test_container_detector_not_run_when_detection_development_is_none(
316+
self,
317+
):
318+
resource = create_resource(ResourceConfig(detection_development=None))
319+
self.assertNotIn(CONTAINER_ID, resource.attributes)
320+
321+
def test_container_detector_not_run_when_detectors_list_empty(self):
322+
config = ResourceConfig(
323+
detection_development=ExperimentalResourceDetection(detectors=[])
324+
)
325+
resource = create_resource(config)
326+
self.assertNotIn(CONTAINER_ID, resource.attributes)
327+
328+
def test_container_detector_warns_when_package_missing(self):
329+
"""A warning is logged when the contrib package is not installed."""
330+
with patch.dict(
331+
"sys.modules",
332+
{"opentelemetry.resource.detector.containerid": None},
333+
):
334+
with self.assertLogs(
335+
"opentelemetry.sdk._configuration._resource", level="WARNING"
336+
) as cm:
337+
resource = create_resource(self._config_with_container())
338+
self.assertNotIn(CONTAINER_ID, resource.attributes)
339+
self.assertTrue(
340+
any(
341+
"opentelemetry-resource-detector-containerid" in msg
342+
for msg in cm.output
343+
)
344+
)
345+
346+
def test_container_detector_uses_contrib_when_available(self):
347+
"""When the contrib package is installed, container.id is detected."""
348+
mock_resource = Resource({CONTAINER_ID: "abc123"})
349+
mock_detector = MagicMock()
350+
mock_detector.return_value.detect.return_value = mock_resource
351+
mock_module = MagicMock()
352+
mock_module.ContainerResourceDetector = mock_detector
353+
354+
with patch.dict(
355+
"sys.modules",
356+
{"opentelemetry.resource.detector.containerid": mock_module},
357+
):
358+
resource = create_resource(self._config_with_container())
359+
360+
self.assertEqual(resource.attributes[CONTAINER_ID], "abc123")
361+
362+
def test_explicit_attributes_override_container_detector(self):
363+
"""Config attributes win over detector-provided values."""
364+
mock_resource = Resource({CONTAINER_ID: "detected-id"})
365+
mock_detector = MagicMock()
366+
mock_detector.return_value.detect.return_value = mock_resource
367+
mock_module = MagicMock()
368+
mock_module.ContainerResourceDetector = mock_detector
369+
370+
config = ResourceConfig(
371+
attributes=[
372+
AttributeNameValue(name="container.id", value="explicit-id")
373+
],
374+
detection_development=ExperimentalResourceDetection(
375+
detectors=[ExperimentalResourceDetector(container={})]
376+
),
377+
)
378+
with patch.dict(
379+
"sys.modules",
380+
{"opentelemetry.resource.detector.containerid": mock_module},
381+
):
382+
resource = create_resource(config)
383+
384+
self.assertEqual(resource.attributes[CONTAINER_ID], "explicit-id")

0 commit comments

Comments
 (0)