Skip to content

Commit 0bc8ec5

Browse files
authored
Handle ELASTIC_OTEL_OPAMP_HEADERS env var (#411)
* distro: handle ELASTIC_OTEL_OPAMP_HEADERS env var To set OpAMP client headers like authentication. * Add docs * Update docs/reference/edot-python/configuration.md
1 parent fc2f7d2 commit 0bc8ec5

File tree

5 files changed

+68
-3
lines changed

5 files changed

+68
-3
lines changed

docs/reference/edot-python/configuration.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Because the {{edot}} Python is an extension of OpenTelemetry Python, it supports
3939

4040
```{applies_to}
4141
serverless: unavailable
42-
stack: preview 9.1
42+
stack: preview 9.1
4343
product:
4444
edot_python: preview 1.4.0
4545
```
@@ -56,6 +56,21 @@ export ELASTIC_OTEL_OPAMP_ENDPOINT=http://localhost:4320/v1/opamp
5656

5757
To deactivate central configuration, remove the `ELASTIC_OTEL_OPAMP_ENDPOINT` environment variable and restart the instrumented application.
5858

59+
### Central configuration authentication
60+
61+
```{applies_to}
62+
serverless: unavailable
63+
stack: preview 9.1
64+
product:
65+
edot_python: preview 1.10.0
66+
```
67+
68+
If the OpAMP server is configured to require authentication set the `ELASTIC_OTEL_OPAMP_HEADERS` environment variable.
69+
70+
```
71+
export ELASTIC_OTEL_OPAMP_HEADERS="Authorization=ApiKey an_api_key"
72+
```
73+
5974
### Central configuration settings
6075

6176
You can modify the following settings for EDOT Python through APM Agent Central Configuration:

src/elasticotel/distro/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616

17+
from __future__ import annotations
18+
1719
import logging
1820
import os
1921
from urllib.parse import urlparse, urlunparse
@@ -48,13 +50,15 @@
4850
)
4951
from opentelemetry.sdk.resources import OTELResourceDetector
5052
from opentelemetry.util._importlib_metadata import EntryPoint
53+
from opentelemetry.util.re import parse_env_headers
5154
from opentelemetry._opamp.agent import OpAMPAgent
5255
from opentelemetry._opamp.client import OpAMPClient
5356
from opentelemetry._opamp.proto import opamp_pb2 as opamp_pb2
5457

5558
from elasticotel.distro import version
5659
from elasticotel.distro.environment_variables import (
5760
ELASTIC_OTEL_OPAMP_ENDPOINT,
61+
ELASTIC_OTEL_OPAMP_HEADERS,
5862
ELASTIC_OTEL_SYSTEM_METRICS_ENABLED,
5963
)
6064
from elasticotel.distro.resource_detectors import get_cloud_resource_detectors
@@ -117,9 +121,17 @@ def _configure(self, **kwargs):
117121
):
118122
agent_identifying_attributes["deployment.environment.name"] = deployment_environment_name
119123

124+
# handle headers for the OpAMP client from the environment variable
125+
headers_env: str | None = os.environ.get(ELASTIC_OTEL_OPAMP_HEADERS)
126+
if headers_env:
127+
headers = parse_env_headers(headers_env, liberal=True)
128+
else:
129+
headers = None
130+
120131
opamp_client = OpAMPClient(
121132
endpoint=endpoint_url,
122133
agent_identifying_attributes=agent_identifying_attributes,
134+
headers=headers,
123135
)
124136
opamp_agent = OpAMPAgent(
125137
interval=30,

src/elasticotel/distro/environment_variables.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,12 @@
3131
3232
**Default value:** ``not set``
3333
"""
34+
35+
ELASTIC_OTEL_OPAMP_HEADERS = "ELASTIC_OTEL_OPAMP_HEADERS"
36+
"""
37+
.. envvar:: ELASTIC_OTEL_OPAMP_HEADERS
38+
39+
HTTP headers to be sento do the OpAMP endpoint.
40+
41+
**Default value:** ``not set``
42+
"""

src/opentelemetry/_opamp/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
_DEFAULT_OPAMP_TIMEOUT_MS = 1_000
3939

40-
_OTLP_HTTP_HEADERS = {
40+
_OPAMP_HTTP_HEADERS = {
4141
"Content-Type": "application/x-protobuf",
4242
"User-Agent": "OTel-OpAMP-Python/" + __version__,
4343
}
@@ -66,7 +66,7 @@ def __init__(
6666

6767
self._endpoint = endpoint
6868
headers = headers or {}
69-
self._headers = {**_OTLP_HTTP_HEADERS, **headers}
69+
self._headers = {**_OPAMP_HTTP_HEADERS, **headers}
7070

7171
self._agent_description = messages._build_agent_description(
7272
identifying_attributes=agent_identifying_attributes,

tests/distro/test_distro.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ def test_configurator_sets_up_opamp_with_http_endpoint(self, client_mock, agent_
135135
client_mock.assert_called_once_with(
136136
endpoint="http://localhost:4320/v1/opamp",
137137
agent_identifying_attributes={"service.name": "service", "deployment.environment.name": "dev"},
138+
headers=None,
138139
)
139140
agent_mock.assert_called_once_with(interval=30, message_handler=opamp_handler, client=client_mock)
140141
agent_mock.start.assert_called_once_with()
@@ -158,6 +159,32 @@ def test_configurator_sets_up_opamp_with_https_endpoint(self, client_mock, agent
158159
client_mock.assert_called_once_with(
159160
endpoint="https://localhost:4320/v1/opamp",
160161
agent_identifying_attributes={"service.name": "service", "deployment.environment.name": "dev"},
162+
headers=None,
163+
)
164+
agent_mock.assert_called_once_with(interval=30, message_handler=opamp_handler, client=client_mock)
165+
agent_mock.start.assert_called_once_with()
166+
167+
@mock.patch.dict(
168+
"os.environ",
169+
{
170+
"ELASTIC_OTEL_OPAMP_ENDPOINT": "http://localhost:4320/v1/opamp",
171+
"ELASTIC_OTEL_OPAMP_HEADERS": "Authorization=ApiKey foobar===",
172+
"OTEL_RESOURCE_ATTRIBUTES": "service.name=service,deployment.environment.name=dev",
173+
},
174+
clear=True,
175+
)
176+
@mock.patch("elasticotel.distro.OpAMPAgent")
177+
@mock.patch("elasticotel.distro.OpAMPClient")
178+
def test_configurator_sets_up_opamp_with_headers_from_environment_variable(self, client_mock, agent_mock):
179+
client_mock.return_value = client_mock
180+
agent_mock.return_value = agent_mock
181+
182+
ElasticOpenTelemetryConfigurator()._configure()
183+
184+
client_mock.assert_called_once_with(
185+
endpoint="http://localhost:4320/v1/opamp",
186+
agent_identifying_attributes={"service.name": "service", "deployment.environment.name": "dev"},
187+
headers={"authorization": "ApiKey foobar==="},
161188
)
162189
agent_mock.assert_called_once_with(interval=30, message_handler=opamp_handler, client=client_mock)
163190
agent_mock.start.assert_called_once_with()
@@ -181,6 +208,7 @@ def test_configurator_adds_path_to_opamp_endpoint_if_missing(self, client_mock,
181208
client_mock.assert_called_once_with(
182209
endpoint="https://localhost:4320/v1/opamp",
183210
agent_identifying_attributes={"service.name": "service", "deployment.environment.name": "dev"},
211+
headers=None,
184212
)
185213
agent_mock.assert_called_once_with(interval=30, message_handler=opamp_handler, client=client_mock)
186214
agent_mock.start.assert_called_once_with()
@@ -204,6 +232,7 @@ def test_configurator_sets_up_opamp_without_deployment_environment_name(self, cl
204232
client_mock.assert_called_once_with(
205233
endpoint="https://localhost:4320/v1/opamp",
206234
agent_identifying_attributes={"service.name": "service"},
235+
headers=None,
207236
)
208237
agent_mock.assert_called_once_with(interval=30, message_handler=opamp_handler, client=client_mock)
209238
agent_mock.start.assert_called_once_with()

0 commit comments

Comments
 (0)