Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5c26f00
config: add resource and propagator creation from declarative config
MikeGoldsmith Mar 13, 2026
8232012
update changelog with PR number
MikeGoldsmith Mar 13, 2026
8329ae4
fix pylint, pyright and ruff errors in resource/propagator config
MikeGoldsmith Mar 13, 2026
506d816
address review feedback: use _DEFAULT_RESOURCE, fix bool_array coercion
MikeGoldsmith Mar 16, 2026
8232d48
fix linter
MikeGoldsmith Mar 16, 2026
6ed3425
Merge branch 'main' of github.com:open-telemetry/opentelemetry-python…
MikeGoldsmith Mar 16, 2026
99753f9
address review feedback: single coercion table, simplify attributes m…
MikeGoldsmith Mar 16, 2026
8ba91d8
use Callable type annotation on _array helper
MikeGoldsmith Mar 17, 2026
516aecc
Merge remote-tracking branch 'upstream/main' into mike/config-resourc…
MikeGoldsmith Mar 20, 2026
9cfdcce
add detection infrastructure foundations for resource detectors
MikeGoldsmith Mar 20, 2026
103ff08
move service.name default into base resource
MikeGoldsmith Mar 20, 2026
7f51034
remove unused logging import from _propagator.py
MikeGoldsmith Mar 20, 2026
e42f1d8
wire container resource detector in declarative config
MikeGoldsmith Mar 20, 2026
327e168
add changelog entry for container resource detector (#5004)
MikeGoldsmith Mar 20, 2026
473a78e
merge upstream/main and fix pylint no-self-use in TestContainerResour…
MikeGoldsmith Mar 29, 2026
22fe70d
add pylint disables for lazy container import in _resource.py
MikeGoldsmith Mar 29, 2026
c59bdf1
merge upstream/main
MikeGoldsmith Apr 1, 2026
ce15811
load container detector via entry point instead of lazy import
MikeGoldsmith Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- `opentelemetry-sdk`: Add `container` resource detector support to declarative file configuration via `detection_development.detectors[].container`, using entry point loading of the `opentelemetry-resource-detector-containerid` contrib package
([#5004](https://github.com/open-telemetry/opentelemetry-python/pull/5004))
- `opentelemetry-sdk`: Add shared `_parse_headers` helper for declarative config OTLP exporters
([#5021](https://github.com/open-telemetry/opentelemetry-python/pull/5021))
- `opentelemetry-api`: Replace a broad exception in attribute cleaning tests to satisfy pylint in the `lint-opentelemetry-api` CI job
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
SERVICE_NAME,
Resource,
)
from opentelemetry.util._importlib_metadata import entry_points

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -149,6 +150,30 @@ def _run_detectors(
is updated in-place; later detectors overwrite earlier ones for the
same key.
"""
if detector_config.container is not None:
# The container detector is not part of the core SDK. It is provided
# by the opentelemetry-resource-detector-containerid contrib package,
# which registers itself under the opentelemetry_resource_detector
# entry point group as "container". Loading via entry point matches
# the env-var config counterpart (OTEL_EXPERIMENTAL_RESOURCE_DETECTORS)
# and avoids a hard import dependency on contrib. See also:
# https://github.com/open-telemetry/opentelemetry-configuration/issues/570
ep = next(
iter(
entry_points(
group="opentelemetry_resource_detector", name="container"
)
),
None,
)
if ep is None:
_logger.warning(
"container resource detector requested but "
"'opentelemetry-resource-detector-containerid' is not "
"installed; install it to enable container detection"
)
else:
detected_attrs.update(ep.load()().detect().attributes)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the env var version uses get_aggregated_resources(). Maybe this should too for consistency -- wdyt?



def _filter_attributes(
Expand Down
90 changes: 89 additions & 1 deletion opentelemetry-sdk/tests/_configuration/test_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@

import os
import unittest
from unittest.mock import patch
from unittest.mock import MagicMock, patch

from opentelemetry.sdk._configuration._resource import create_resource
from opentelemetry.sdk._configuration.models import (
AttributeNameValue,
AttributeType,
ExperimentalResourceDetection,
ExperimentalResourceDetector,
)
from opentelemetry.sdk._configuration.models import Resource as ResourceConfig
from opentelemetry.sdk.resources import (
CONTAINER_ID,
SERVICE_NAME,
TELEMETRY_SDK_LANGUAGE,
TELEMETRY_SDK_NAME,
Expand Down Expand Up @@ -295,3 +298,88 @@ def test_attributes_list_invalid_pair_skipped(self):
self.assertEqual(resource.attributes["foo"], "bar")
self.assertNotIn("no-equals", resource.attributes)
self.assertTrue(any("no-equals" in msg for msg in cm.output))


class TestContainerResourceDetector(unittest.TestCase):
@staticmethod
def _config_with_container() -> ResourceConfig:
return ResourceConfig(
detection_development=ExperimentalResourceDetection(
detectors=[ExperimentalResourceDetector(container={})]
)
)

def test_container_detector_not_run_when_absent(self):
resource = create_resource(ResourceConfig())
self.assertNotIn(CONTAINER_ID, resource.attributes)

def test_container_detector_not_run_when_detection_development_is_none(
self,
):
resource = create_resource(ResourceConfig(detection_development=None))
self.assertNotIn(CONTAINER_ID, resource.attributes)

def test_container_detector_not_run_when_detectors_list_empty(self):
config = ResourceConfig(
detection_development=ExperimentalResourceDetection(detectors=[])
)
resource = create_resource(config)
self.assertNotIn(CONTAINER_ID, resource.attributes)

def test_container_detector_warns_when_package_missing(self):
"""A warning is logged when the contrib entry point is not found."""
with patch(
"opentelemetry.sdk._configuration._resource.entry_points",
return_value=[],
):
with self.assertLogs(
"opentelemetry.sdk._configuration._resource", level="WARNING"
) as cm:
resource = create_resource(self._config_with_container())
self.assertNotIn(CONTAINER_ID, resource.attributes)
self.assertTrue(
any(
"opentelemetry-resource-detector-containerid" in msg
for msg in cm.output
)
)

def test_container_detector_uses_contrib_when_available(self):
"""When the contrib entry point is registered, container.id is detected."""
mock_resource = Resource({CONTAINER_ID: "abc123"})
mock_detector = MagicMock()
mock_detector.return_value.detect.return_value = mock_resource
mock_ep = MagicMock()
mock_ep.load.return_value = mock_detector

with patch(
"opentelemetry.sdk._configuration._resource.entry_points",
return_value=[mock_ep],
):
resource = create_resource(self._config_with_container())

self.assertEqual(resource.attributes[CONTAINER_ID], "abc123")

def test_explicit_attributes_override_container_detector(self):
"""Config attributes win over detector-provided values."""
mock_resource = Resource({CONTAINER_ID: "detected-id"})
mock_detector = MagicMock()
mock_detector.return_value.detect.return_value = mock_resource
mock_ep = MagicMock()
mock_ep.load.return_value = mock_detector

config = ResourceConfig(
attributes=[
AttributeNameValue(name="container.id", value="explicit-id")
],
detection_development=ExperimentalResourceDetection(
detectors=[ExperimentalResourceDetector(container={})]
),
)
with patch(
"opentelemetry.sdk._configuration._resource.entry_points",
return_value=[mock_ep],
):
resource = create_resource(config)

self.assertEqual(resource.attributes[CONTAINER_ID], "explicit-id")
Loading